docs: added to workflow legend + example improvements (#11895)

This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-03-19 08:31:28 +02:00
1 parent 9ead47c51e
commit 4827db98f7
19 files changed
+11339 -11302

No files matched your search

@@ -101,24 +101,25 @@ jest.setTimeout(60 * 1000)
## Test a POST API Route ## Test a POST API Route
Suppose you have a `hello` module whose main service extends the service factory, and that has the following model: Suppose you have a `blog` module whose main service extends the service factory, and that has the following model:
```ts title="src/modules/hello/models/my-custom.ts" ```ts title="src/modules/blog/models/my-custom.ts"
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
id: model.id().primaryKey(), id: model.id().primaryKey(),
name: model.text(), name: model.text(),
}) })
export default MyCustom export default Post
``` ```
And consider that the file `src/api/custom/route.ts` defines another route handler for `POST` requests: And consider that the file `src/api/custom/route.ts` defines another route handler for `POST` requests:
```ts title="src/api/custom/route.ts" ```ts title="src/api/custom/route.ts"
// other imports... // other imports...
import HelloModuleService from "../../../modules/hello/service" import BlogModuleService from "../../../modules/blog/service"
import { BLOG_MODULE } from "../../../modules/blog"
// ... // ...
@@ -126,21 +127,21 @@ export async function POST(
req: MedusaRequest, req: MedusaRequest,
res: MedusaResponse res: MedusaResponse
) { ) {
const helloModuleService: HelloModuleService = req.scope.resolve( const blogModuleService: BlogModuleService = req.scope.resolve(
"helloModuleService" BLOG_MODULE
) )
const myCustom = await helloModuleService.createMyCustoms( const post = await blogModuleService.createPosts(
req.body req.body
) )
res.json({ res.json({
my_custom: myCustom, post,
}) })
} }
``` ```
This API route creates a new record of `MyCustom`. This API route creates a new record of `Post`.
To write tests for this API route, add the following at the end of the `testSuite` function in `integration-tests/http/custom-routes.spec.ts`: To write tests for this API route, add the following at the end of the `testSuite` function in `integration-tests/http/custom-routes.spec.ts`:
@@ -150,7 +151,7 @@ export const postHighlights = [
```ts title="integration-tests/http/custom-routes.spec.ts" highlights={postHighlights} ```ts title="integration-tests/http/custom-routes.spec.ts" highlights={postHighlights}
// other imports... // other imports...
import HelloModuleService from "../../src/modules/hello/service" import BlogModuleService from "../../src/modules/blog/service"
medusaIntegrationTestRunner({ medusaIntegrationTestRunner({
testSuite: ({ api, getContainer }) => { testSuite: ({ api, getContainer }) => {
@@ -171,8 +172,8 @@ medusaIntegrationTestRunner({
) )
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data).toHaveProperty("my_custom") expect(response.data).toHaveProperty("post")
expect(response.data.my_custom).toEqual({ expect(response.data.post).toEqual({
id, id,
name: "Test", name: "Test",
created_at: expect.any(String), created_at: expect.any(String),
@@ -190,7 +191,7 @@ This adds a test for the `POST /custom` API route. It uses `api.post` to send th
The test passes if the response has: The test passes if the response has:
- Status code `200`. - Status code `200`.
- A `my_custom` property in its data. - A `post` property in its data.
- Its `id` and `name` match the ones provided to the request. - Its `id` and `name` match the ones provided to the request.
### Tear Down Created Record ### Tear Down Created Record
@@ -203,7 +204,8 @@ So, add an `afterAll` hook in the `describe` block for `POST /custom`:
```ts title="integration-tests/http/custom-routes.spec.ts" ```ts title="integration-tests/http/custom-routes.spec.ts"
// other imports... // other imports...
import HelloModuleService from "../../src/modules/hello/service" import BlogModuleService from "../../src/modules/blog/service"
import { BLOG_MODULE } from "../../src/modules/blog"
medusaIntegrationTestRunner({ medusaIntegrationTestRunner({
testSuite: ({ api, getContainer }) => { testSuite: ({ api, getContainer }) => {
@@ -213,11 +215,11 @@ medusaIntegrationTestRunner({
describe("POST /custom", () => { describe("POST /custom", () => {
// ... // ...
afterAll(() => async () => { afterAll(() => async () => {
const helloModuleService: HelloModuleService = getContainer().resolve( const blogModuleService: BlogModuleService = getContainer().resolve(
"helloModuleService" BLOG_MODULE
) )
await helloModuleService.deleteMyCustoms(id) await blogModuleService.deletePosts(id)
}) })
}) })
}) })
@@ -225,7 +227,7 @@ medusaIntegrationTestRunner({
}) })
``` ```
The `afterAll` hook resolves the `HelloModuleService` and use its `deleteMyCustoms` to delete the record created by the test. The `afterAll` hook resolves the `BlogModuleService` and use its `deletePosts` to delete the record created by the test.
--- ---
@@ -235,17 +237,18 @@ Consider a `/custom/:id` API route created at `src/api/custom/[id]/route.ts`:
```ts title="src/api/custom/[id]/route.ts" ```ts title="src/api/custom/[id]/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import HelloModuleService from "../../../modules/hello/service" import BlogModuleService from "../../../modules/blog/service"
import { BLOG_MODULE } from "../../../modules/blog"
export async function DELETE( export async function DELETE(
req: MedusaRequest, req: MedusaRequest,
res: MedusaResponse res: MedusaResponse
) { ) {
const helloModuleService: HelloModuleService = req.scope.resolve( const blogModuleService: BlogModuleService = req.scope.resolve(
"helloModuleService" BLOG_MODULE
) )
await helloModuleService.deleteMyCustoms(req.params.id) await blogModuleService.deletePosts(req.params.id)
res.json({ res.json({
success: true, success: true,
@@ -253,7 +256,7 @@ export async function DELETE(
} }
``` ```
This API route accepts an ID path parameter, and uses the `HelloModuleService` to delete a `MyCustom` record by that ID. This API route accepts an ID path parameter, and uses the `BlogModuleService` to delete a `Post` record by that ID.
To add tests for this API route, add the following to `integration-tests/http/custom-routes.spec.ts`: To add tests for this API route, add the following to `integration-tests/http/custom-routes.spec.ts`:
@@ -271,11 +274,11 @@ medusaIntegrationTestRunner({
const id = "1" const id = "1"
beforeAll(() => async () => { beforeAll(() => async () => {
const helloModuleService: HelloModuleService = getContainer().resolve( const blogModuleService: BlogModuleService = getContainer().resolve(
"helloModuleService" BLOG_MODULE
) )
await helloModuleService.createMyCustoms({ await blogModuleService.createPosts({
id, id,
name: "Test", name: "Test",
}) })
@@ -296,7 +299,7 @@ medusaIntegrationTestRunner({
}) })
``` ```
This adds a new test for the `DELETE /custom/:id` API route. You use the `beforeAll` hook to create a `MyCustom` record using the `HelloModuleService`. This adds a new test for the `DELETE /custom/:id` API route. You use the `beforeAll` hook to create a `Post` record using the `BlogModuleService`.
In the test, you use the `api.delete` method to send a `DELETE` request to `/custom/:id`. The test passes if the response: In the test, you use the `api.delete` method to send a `DELETE` request to `/custom/:id`. The test passes if the response:
@@ -19,13 +19,13 @@ In this chapter, find an example of writing an integration test for a module usi
## Write Integration Test for Module ## Write Integration Test for Module
Consider a `hello` module with a `HelloModuleService` that has a `getMessage` method: Consider a `blog` module with a `BlogModuleService` that has a `getMessage` method:
```ts title="src/modules/hello/service.ts" ```ts title="src/modules/blog/service.ts"
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import MyCustom from "./models/my-custom"
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, MyCustom,
}){ }){
getMessage(): string { getMessage(): string {
@@ -33,23 +33,23 @@ class HelloModuleService extends MedusaService({
} }
} }
export default HelloModuleService export default BlogModuleService
``` ```
To create an integration test for the method, create the file `src/modules/hello/__tests__/service.spec.ts` with the following content: To create an integration test for the method, create the file `src/modules/blog/__tests__/service.spec.ts` with the following content:
```ts title="src/modules/hello/__tests__/service.spec.ts" ```ts title="src/modules/blog/__tests__/service.spec.ts"
import { moduleIntegrationTestRunner } from "@medusajs/test-utils" import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import { HELLO_MODULE } from ".." import { BLOG_MODULE } from ".."
import HelloModuleService from "../service" import BlogModuleService from "../service"
import MyCustom from "../models/my-custom" import MyCustom from "../models/my-custom"
moduleIntegrationTestRunner<HelloModuleService>({ moduleIntegrationTestRunner<BlogModuleService>({
moduleName: HELLO_MODULE, moduleName: BLOG_MODULE,
moduleModels: [MyCustom], moduleModels: [MyCustom],
resolve: "./src/modules/hello", resolve: "./src/modules/blog",
testSuite: ({ service }) => { testSuite: ({ service }) => {
describe("HelloModuleService", () => { describe("BlogModuleService", () => {
it("says hello world", () => { it("says hello world", () => {
const message = service.getMessage() const message = service.getMessage()
@@ -62,7 +62,7 @@ moduleIntegrationTestRunner<HelloModuleService>({
jest.setTimeout(60 * 1000) jest.setTimeout(60 * 1000)
``` ```
You use the `moduleIntegrationTestRunner` function to add tests for the `hello` module. You have one test that passes if the `getMessage` method returns the `"Hello, World!"` string. You use the `moduleIntegrationTestRunner` function to add tests for the `blog` module. You have one test that passes if the `getMessage` method returns the `"Hello, World!"` string.
--- ---
@@ -21,18 +21,18 @@ In this chapter, you'll learn about `moduleIntegrationTestRunner` from Medusa's
`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled. `moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled.
For example, assuming you have a `hello` module, create a test file at `src/modules/hello/__tests__/service.spec.ts`: For example, assuming you have a `blog` module, create a test file at `src/modules/blog/__tests__/service.spec.ts`:
```ts title="src/modules/hello/__tests__/service.spec.ts" ```ts title="src/modules/blog/__tests__/service.spec.ts"
import { moduleIntegrationTestRunner } from "@medusajs/test-utils" import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import { HELLO_MODULE } from ".." import { BLOG_MODULE } from ".."
import HelloModuleService from "../service" import BlogModuleService from "../service"
import MyCustom from "../models/my-custom" import Post from "../models/post"
moduleIntegrationTestRunner<HelloModuleService>({ moduleIntegrationTestRunner<BlogModuleService>({
moduleName: HELLO_MODULE, moduleName: BLOG_MODULE,
moduleModels: [MyCustom], moduleModels: [Post],
resolve: "./src/modules/hello", resolve: "./src/modules/blog",
testSuite: ({ service }) => { testSuite: ({ service }) => {
// TODO write tests // TODO write tests
}, },
@@ -86,9 +86,9 @@ For example:
```ts ```ts
import { moduleIntegrationTestRunner } from "@medusajs/test-utils" import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import HelloModuleService from "../service" import BlogModuleService from "../service"
moduleIntegrationTestRunner<HelloModuleService>({ moduleIntegrationTestRunner<BlogModuleService>({
moduleOptions: { moduleOptions: {
apiKey: "123", apiKey: "123",
}, },
@@ -106,14 +106,14 @@ For example:
```ts ```ts
import { moduleIntegrationTestRunner } from "@medusajs/test-utils" import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import HelloModuleService from "../service" import BlogModuleService from "../service"
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const DummyModel = model.define("dummy_model", { const DummyModel = model.define("dummy_model", {
id: model.id().primaryKey(), id: model.id().primaryKey(),
}) })
moduleIntegrationTestRunner<HelloModuleService>({ moduleIntegrationTestRunner<BlogModuleService>({
moduleModels: [DummyModel], moduleModels: [DummyModel],
// ... // ...
}) })
@@ -195,7 +195,7 @@ If you have a many-to-many relation with a `pivotEntity` specified, make sure to
For example, assuming you have the [Order, Product, and OrderProduct models from the previous chapter](../relationships/page.mdx#many-to-many-with-custom-columns), add `OrderProduct` to `MedusaService`'s object parameter: For example, assuming you have the [Order, Product, and OrderProduct models from the previous chapter](../relationships/page.mdx#many-to-many-with-custom-columns), add `OrderProduct` to `MedusaService`'s object parameter:
```ts highlights={["4"]} ```ts highlights={["4"]}
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
Order, Order,
Product, Product,
OrderProduct, OrderProduct,
@@ -208,7 +208,7 @@ For example:
```ts ```ts
// create order-product association // create order-product association
const orderProduct = await helloModuleService.createOrderProducts({ const orderProduct = await blogModuleService.createOrderProducts({
order_id: "123", order_id: "123",
product_id: "123", product_id: "123",
metadata: { metadata: {
@@ -217,7 +217,7 @@ const orderProduct = await helloModuleService.createOrderProducts({
}) })
// update order-product association // update order-product association
const orderProduct = await helloModuleService.updateOrderProducts({ const orderProduct = await blogModuleService.updateOrderProducts({
id: "123", id: "123",
metadata: { metadata: {
test: false, test: false,
@@ -225,7 +225,7 @@ const orderProduct = await helloModuleService.updateOrderProducts({
}) })
// delete order-product association // delete order-product association
await helloModuleService.deleteOrderProducts("123") await blogModuleService.deleteOrderProducts("123")
``` ```
Since the `OrderProduct` data model belongs to the `Order` and `Product` data models, you can set its order and product as explained in the [one-to-many relationship section](#manage-one-to-many-relationship) using `order_id` and `product_id`. Since the `OrderProduct` data model belongs to the `Order` and `Product` data models, you can set its order and product as explained in the [one-to-many relationship section](#manage-one-to-many-relationship) using `order_id` and `product_id`.
@@ -247,7 +247,7 @@ export const retrieveHighlights = [
] ]
```ts highlights={retrieveHighlights} ```ts highlights={retrieveHighlights}
const product = await helloModuleService.retrieveProducts( const product = await blogModuleService.retrieveProducts(
"123", "123",
{ {
relations: ["orders"], relations: ["orders"],
@@ -96,12 +96,12 @@ To emit a service event:
<CodeTabs group="service_type"> <CodeTabs group="service_type">
<CodeTab label="Extending Service Factory" value="service_factory"> <CodeTab label="Extending Service Factory" value="service_factory">
```ts title="src/modules/hello/service.ts" highlights={["9"]} ```ts title="src/modules/blog/service.ts" highlights={["9"]}
import { IEventBusService } from "@medusajs/framework/types" import { IEventBusService } from "@medusajs/framework/types"
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
protected eventBusService_: AbstractEventBusModuleService protected eventBusService_: AbstractEventBusModuleService
@@ -115,10 +115,10 @@ class HelloModuleService extends MedusaService({
</CodeTab> </CodeTab>
<CodeTab label="Without Service Factory" value="no_service_factory"> <CodeTab label="Without Service Factory" value="no_service_factory">
```ts title="src/modules/hello/service.ts" highlights={["6"]} ```ts title="src/modules/blog/service.ts" highlights={["6"]}
import { IEventBusService } from "@medusajs/framework/types" import { IEventBusService } from "@medusajs/framework/types"
class HelloModuleService { class BlogModuleService {
protected eventBusService_: AbstractEventBusModuleService protected eventBusService_: AbstractEventBusModuleService
constructor({ event_bus }) { constructor({ event_bus }) {
@@ -138,8 +138,8 @@ export const serviceHighlights = [
["8", "data", "The data payload to pass with the event."] ["8", "data", "The data payload to pass with the event."]
] ]
```ts title="src/modules/hello/service.ts" highlights={serviceHighlights} ```ts title="src/modules/blog/service.ts" highlights={serviceHighlights}
class HelloModuleService { class BlogModuleService {
// ... // ...
performAction() { performAction() {
// TODO perform action // TODO perform action
@@ -173,7 +173,7 @@ module.exports = defineConfig({
// ... // ...
modules: [ modules: [
{ {
resolve: "./src/modules/hello", resolve: "./src/modules/blog",
dependencies: [ dependencies: [
Modules.EVENT_BUS, Modules.EVENT_BUS,
], ],
@@ -31,13 +31,13 @@ type InjectedDependencies = {
logger: Logger logger: Logger
} }
export default class HelloModuleService { export default class BlogModuleService {
protected logger_: Logger protected logger_: Logger
constructor({ logger }: InjectedDependencies) { constructor({ logger }: InjectedDependencies) {
this.logger_ = logger this.logger_ = logger
this.logger_.info("[HelloModuleService]: Hello World!") this.logger_.info("[BlogModuleService]: Hello World!")
} }
// ... // ...
@@ -40,7 +40,7 @@ import {
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import { SqlEntityManager } from "@mikro-orm/knex" import { SqlEntityManager } from "@mikro-orm/knex"
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectManager() @InjectManager()
@@ -105,7 +105,7 @@ import {
import { Context } from "@medusajs/framework/types" import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex" import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectTransactionManager() @InjectTransactionManager()
protected async update_( protected async update_(
@@ -147,7 +147,7 @@ class HelloModuleService {
} }
``` ```
The `HelloModuleService` has two methods: The `BlogModuleService` has two methods:
- A protected `update_` that performs the database operations inside a transaction. - A protected `update_` that performs the database operations inside a transaction.
- A public `update` that executes the transactional protected method. - A public `update` that executes the transactional protected method.
@@ -180,7 +180,7 @@ For example, the `update` method could be changed to the following:
// other imports... // other imports...
import { EntityManager } from "@mikro-orm/knex" import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectManager() @InjectManager()
async update( async update(
@@ -210,7 +210,7 @@ For example:
// other imports... // other imports...
import { EntityManager } from "@mikro-orm/knex" import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectTransactionManager() @InjectTransactionManager()
protected async anotherMethod( protected async anotherMethod(
@@ -253,15 +253,15 @@ For example, resolve the `baseRepository` in your service's constructor:
```ts highlights={[["14"]]} ```ts highlights={[["14"]]}
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
import { DAL } from "@medusajs/framework/types" import { DAL } from "@medusajs/framework/types"
type InjectedDependencies = { type InjectedDependencies = {
baseRepository: DAL.RepositoryService baseRepository: DAL.RepositoryService
} }
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
protected baseRepository_: DAL.RepositoryService protected baseRepository_: DAL.RepositoryService
@@ -271,7 +271,7 @@ class HelloModuleService extends MedusaService({
} }
} }
export default HelloModuleService export default BlogModuleService
``` ```
</CodeTab> </CodeTab>
<CodeTab label="Without Service Factory" value="no-service-factory"> <CodeTab label="Without Service Factory" value="no-service-factory">
@@ -283,7 +283,7 @@ type InjectedDependencies = {
baseRepository: DAL.RepositoryService baseRepository: DAL.RepositoryService
} }
class HelloModuleService { class BlogModuleService {
protected baseRepository_: DAL.RepositoryService protected baseRepository_: DAL.RepositoryService
constructor({ baseRepository }: InjectedDependencies) { constructor({ baseRepository }: InjectedDependencies) {
@@ -291,7 +291,7 @@ class HelloModuleService {
} }
} }
export default HelloModuleService export default BlogModuleService
``` ```
</CodeTab> </CodeTab>
@@ -313,7 +313,7 @@ import {
import { Context } from "@medusajs/framework/types" import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex" import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectTransactionManager() @InjectTransactionManager()
protected async update_( protected async update_(
@@ -383,7 +383,7 @@ The second parameter of the `baseRepository_.transaction` method is an object of
// other imports... // other imports...
import { EntityManager } from "@mikro-orm/knex" import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectTransactionManager() @InjectTransactionManager()
async update_( async update_(
@@ -416,7 +416,7 @@ class HelloModuleService {
// other imports... // other imports...
import { IsolationLevel } from "@mikro-orm/core" import { IsolationLevel } from "@mikro-orm/core"
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectTransactionManager() @InjectTransactionManager()
async update_( async update_(
@@ -442,7 +442,7 @@ class HelloModuleService {
- If `transaction` is provided and this is disabled, the manager in `transaction` is re-used. - If `transaction` is provided and this is disabled, the manager in `transaction` is re-used.
```ts highlights={[["16"]]} ```ts highlights={[["16"]]}
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectTransactionManager() @InjectTransactionManager()
async update_( async update_(
@@ -20,9 +20,9 @@ However, you may use other services in your module to better organize your code
To add an internal service, create it in the `services` directory of your module. To add an internal service, create it in the `services` directory of your module.
For example, create the file `src/modules/hello/services/client.ts` with the following content: For example, create the file `src/modules/blog/services/client.ts` with the following content:
```ts title="src/modules/hello/services/client.ts" ```ts title="src/modules/blog/services/client.ts"
export class ClientService { export class ClientService {
async getMessage(): Promise<string> { async getMessage(): Promise<string> {
return "Hello, World!" return "Hello, World!"
@@ -34,9 +34,9 @@ export class ClientService {
Next, create an `index.ts` file under the `services` directory of the module that exports your internal services. Next, create an `index.ts` file under the `services` directory of the module that exports your internal services.
For example, create the file `src/modules/hello/services/index.ts` with the following content: For example, create the file `src/modules/blog/services/index.ts` with the following content:
```ts title="src/modules/hello/services/index.ts" ```ts title="src/modules/blog/services/index.ts"
export * from "./client" export * from "./client"
``` ```
@@ -48,7 +48,7 @@ Internal services exported in the `services/index.ts` file of your module are no
For example, in your main service: For example, in your main service:
```ts title="src/modules/hello/service.ts" highlights={[["5"], ["13"]]} ```ts title="src/modules/blog/service.ts" highlights={[["5"], ["13"]]}
// other imports... // other imports...
import { ClientService } from "./services" import { ClientService } from "./services"
@@ -56,8 +56,8 @@ type InjectedDependencies = {
clientService: ClientService clientService: ClientService
} }
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
protected clientService_: ClientService protected clientService_: ClientService
@@ -106,7 +106,7 @@ For example:
```ts ```ts
import { ConfigModule } from "@medusajs/framework/types" import { ConfigModule } from "@medusajs/framework/types"
import { HELLO_MODULE } from ".." import { BLOG_MODULE } from ".."
export type InjectedDependencies = { export type InjectedDependencies = {
configModule: ConfigModule configModule: ConfigModule
@@ -116,7 +116,7 @@ export class ClientService {
protected options: Record<string, any> protected options: Record<string, any>
constructor({ configModule }: InjectedDependencies) { constructor({ configModule }: InjectedDependencies) {
const moduleDef = configModule.modules[HELLO_MODULE] const moduleDef = configModule.modules[BLOG_MODULE]
if (typeof moduleDef !== "boolean") { if (typeof moduleDef !== "boolean") {
this.options = moduleDef.options this.options = moduleDef.options
@@ -23,7 +23,7 @@ module.exports = defineConfig({
// ... // ...
modules: [ modules: [
{ {
resolve: "./src/modules/hello", resolve: "./src/modules/blog",
options: { options: {
capitalize: true, capitalize: true,
}, },
@@ -64,17 +64,17 @@ The module’s main service receives the module options as a second parameter.
For example: For example:
```ts title="src/modules/hello/service.ts" highlights={[["12"], ["14", "options?: ModuleOptions"], ["17"], ["18"], ["19"]]} ```ts title="src/modules/blog/service.ts" highlights={[["12"], ["14", "options?: ModuleOptions"], ["17"], ["18"], ["19"]]}
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
// recommended to define type in another file // recommended to define type in another file
type ModuleOptions = { type ModuleOptions = {
capitalize?: boolean capitalize?: boolean
} }
export default class HelloModuleService extends MedusaService({ export default class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
protected options_: ModuleOptions protected options_: ModuleOptions
@@ -98,7 +98,7 @@ The object that a module’s loaders receive as a parameter has an `options` pro
For example: For example:
```ts title="src/modules/hello/loaders/hello-world.ts" highlights={[["11"], ["12", "ModuleOptions", "The type of expected module options."], ["16"]]} ```ts title="src/modules/blog/loaders/hello-world.ts" highlights={[["11"], ["12", "ModuleOptions", "The type of expected module options."], ["16"]]}
import { import {
LoaderOptions, LoaderOptions,
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
@@ -113,7 +113,7 @@ export default async function helloWorldLoader({
}: LoaderOptions<ModuleOptions>) { }: LoaderOptions<ModuleOptions>) {
console.log( console.log(
"[HELLO MODULE] Just started the Medusa application!", "[BLOG MODULE] Just started the Medusa application!",
options options
) )
} }
@@ -129,7 +129,7 @@ So, by performing the validation in the loader, you ensure you can throw an erro
For example, to validate that the Hello Module received an `apiKey` option, create the loader `src/modules/loaders/validate.ts`: For example, to validate that the Hello Module received an `apiKey` option, create the loader `src/modules/loaders/validate.ts`:
```ts title="src/modules/hello/loaders/validate.ts" ```ts title="src/modules/blog/loaders/validate.ts"
import { LoaderOptions } from "@medusajs/framework/types" import { LoaderOptions } from "@medusajs/framework/types"
import { MedusaError } from "@medusajs/framework/utils" import { MedusaError } from "@medusajs/framework/utils"
@@ -152,11 +152,13 @@ export default async function validationLoader({
Then, export the loader in the module's definition file, as explained in [this chapter](../loaders/page.mdx): Then, export the loader in the module's definition file, as explained in [this chapter](../loaders/page.mdx):
```ts title="src/modules/hello/index.ts" ```ts title="src/modules/blog/index.ts"
// other imports... // other imports...
import validationLoader from "./loaders/validate" import validationLoader from "./loaders/validate"
export default Module("hello", { export const BLOG_MODULE = "blog"
export default Module(BLOG_MODULE, {
// ... // ...
loaders: [validationLoader], loaders: [validationLoader],
}) })
@@ -13,17 +13,17 @@ Medusa wraps service method executions to inject useful context or transactions.
For example, if you have a synchronous `getMessage` method, and you use it in other resources like workflows, Medusa executes it as an async method: For example, if you have a synchronous `getMessage` method, and you use it in other resources like workflows, Medusa executes it as an async method:
```ts ```ts
await helloModuleService.getMessage() await blogModuleService.getMessage()
``` ```
So, make sure your service's methods are always async to avoid unexpected errors or behavior. So, make sure your service's methods are always async to avoid unexpected errors or behavior.
```ts highlights={[["8", "", "Method must be async."], ["13", "async", "Correct way of defining the method."]]} ```ts highlights={[["8", "", "Method must be async."], ["13", "async", "Correct way of defining the method."]]}
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
// Don't // Don't
getMessage(): string { getMessage(): string {
@@ -36,5 +36,5 @@ class HelloModuleService extends MedusaService({
} }
} }
export default HelloModuleService export default BlogModuleService
``` ```
@@ -26,31 +26,31 @@ Your service provides data-management functionalities of your data models.
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. 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: For example, create the file `src/modules/blog/service.ts` with the following content:
export const highlights = [ export const highlights = [
["4", "MedusaService", "The service factory function."], ["4", "MedusaService", "The service factory function."],
["5", "MyCustom", "The data models to generate data-management methods for."] ["5", "Post", "The data model to generate data-management methods for."]
] ]
```ts title="src/modules/hello/service.ts" highlights={highlights} ```ts title="src/modules/blog/service.ts" highlights={highlights}
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
// TODO implement custom methods // TODO implement custom methods
} }
export default HelloModuleService export default BlogModuleService
``` ```
### MedusaService Parameters ### MedusaService Parameters
The `MedusaService` function accepts one parameter, which is an object of data models to generate data-management methods for. The `MedusaService` function accepts one parameter, which is an object of data models to generate data-management methods for.
In the example above, since the `HelloModuleService` extends `MedusaService`, it has methods to manage the `MyCustom` data model, such as `createMyCustoms`. In the example above, since the `BlogModuleService` extends `MedusaService`, it has methods to manage the `Post` data model, such as `createPosts`.
### Generated Methods ### Generated Methods
@@ -68,39 +68,39 @@ Find a complete reference of each of the methods in [this documentation](!resour
<Tabs defaultValue="listMyCustoms" layoutType="vertical" className="mt-2"> <Tabs defaultValue="listMyCustoms" layoutType="vertical" className="mt-2">
<TabsList> <TabsList>
<TabsTriggerVertical value="listMyCustoms">listMyCustoms</TabsTriggerVertical> <TabsTriggerVertical value="listPosts">listPosts</TabsTriggerVertical>
<TabsTriggerVertical value="listAndCountMyCustoms">listAndCount</TabsTriggerVertical> <TabsTriggerVertical value="listAndCountPosts">listAndCountPosts</TabsTriggerVertical>
<TabsTriggerVertical value="retrieveMyCustom">retrieveMyCustom</TabsTriggerVertical> <TabsTriggerVertical value="retrievePost">retrievePost</TabsTriggerVertical>
<TabsTriggerVertical value="createMyCustoms">createMyCustoms</TabsTriggerVertical> <TabsTriggerVertical value="createPosts">createPosts</TabsTriggerVertical>
<TabsTriggerVertical value="updateMyCustoms">updateMyCustoms</TabsTriggerVertical> <TabsTriggerVertical value="updatePosts">updatePosts</TabsTriggerVertical>
<TabsTriggerVertical value="deleteMyCustoms">deleteMyCustoms</TabsTriggerVertical> <TabsTriggerVertical value="deletePosts">deletePosts</TabsTriggerVertical>
<TabsTriggerVertical value="softDeleteMyCustoms">softDeleteMyCustoms</TabsTriggerVertical> <TabsTriggerVertical value="softDeletePosts">softDeletePosts</TabsTriggerVertical>
<TabsTriggerVertical value="restoreMyCustoms">restoreMyCustoms</TabsTriggerVertical> <TabsTriggerVertical value="restorePosts">restorePosts</TabsTriggerVertical>
</TabsList> </TabsList>
<TabsContentWrapper className="[&_h3]:!mt-0"> <TabsContentWrapper className="[&_h3]:!mt-0">
<TabsContent value="listMyCustoms"> <TabsContent value="listMyCustoms">
### listMyCustoms ### listPosts
This method retrieves an array of records based on filters and pagination configurations. This method retrieves an array of records based on filters and pagination configurations.
For example: For example:
```ts ```ts
const myCustoms = await helloModuleService const posts = await blogModuleService
.listMyCustoms() .listPosts()
// with filters // with filters
const myCustoms = await helloModuleService const posts = await blogModuleService
.listMyCustoms({ .listPosts({
id: ["123"] id: ["123"]
}) })
``` ```
</TabsContent> </TabsContent>
<TabsContent value="listAndCountMyCustoms"> <TabsContent value="listAndCountPosts">
### listAndCountMyCustoms ### listAndCountPosts
This method retrieves a tuple of an array of records and the total count of available records based on the filters and pagination configurations provided. This method retrieves a tuple of an array of records and the total count of available records based on the filters and pagination configurations provided.
@@ -108,122 +108,122 @@ Find a complete reference of each of the methods in [this documentation](!resour
```ts ```ts
const [ const [
myCustoms, posts,
count count
] = await helloModuleService.listAndCountMyCustoms() ] = await blogModuleService.listAndCountPosts()
// with filters // with filters
const [ const [
myCustoms, posts,
count count
] = await helloModuleService.listAndCountMyCustoms({ ] = await blogModuleService.listAndCountPosts({
id: ["123"] id: ["123"]
}) })
``` ```
</TabsContent> </TabsContent>
<TabsContent value="retrieveMyCustom"> <TabsContent value="retrievePost">
### retrieveMyCustom ### retrievePost
This method retrieves a record by its ID. This method retrieves a record by its ID.
For example: For example:
```ts ```ts
const myCustom = await helloModuleService const post = await blogModuleService
.retrieveMyCustom("123") .retrievePost("123")
``` ```
</TabsContent> </TabsContent>
<TabsContent value="createMyCustoms"> <TabsContent value="createPosts">
### createMyCustoms ### createPosts
This method creates and retrieves records of the data model. This method creates and retrieves records of the data model.
For example: For example:
```ts ```ts
const myCustom = await helloModuleService const post = await blogModuleService
.createMyCustoms({ .createPosts({
name: "test" title: "test"
}) })
// create multiple // create multiple
const myCustoms = await helloModuleService const posts = await blogModuleService
.createMyCustoms([ .createPosts([
{ {
name: "test" title: "test"
}, },
{ {
name: "test 2" title: "test 2"
}, },
]) ])
``` ```
</TabsContent> </TabsContent>
<TabsContent value="updateMyCustoms"> <TabsContent value="updatePosts">
### updateMyCustoms ### updatePosts
This method updates and retrieves records of the data model. This method updates and retrieves records of the data model.
For example: For example:
```ts ```ts
const myCustom = await helloModuleService const post = await blogModuleService
.updateMyCustoms({ .updatePosts({
id: "123", id: "123",
name: "test" title: "test"
}) })
// update multiple // update multiple
const myCustoms = await helloModuleService const posts = await blogModuleService
.updateMyCustoms([ .updatePosts([
{ {
id: "123", id: "123",
name: "test" title: "test"
}, },
{ {
id: "321", id: "321",
name: "test 2" title: "test 2"
}, },
]) ])
// use filters // use filters
const myCustoms = await helloModuleService const posts = await blogModuleService
.updateMyCustoms([ .updatePosts([
{ {
selector: { selector: {
id: ["123", "321"] id: ["123", "321"]
}, },
data: { data: {
name: "test" title: "test"
} }
}, },
]) ])
``` ```
</TabsContent> </TabsContent>
<TabsContent value="deleteMyCustoms"> <TabsContent value="deletePosts">
### deleteMyCustoms ### deletePosts
This method deletes records by an ID or filter. This method deletes records by an ID or filter.
For example: For example:
```ts ```ts
await helloModuleService.deleteMyCustoms("123") await blogModuleService.deletePosts("123")
// delete multiple // delete multiple
await helloModuleService.deleteMyCustoms([ await blogModuleService.deletePosts([
"123", "321" "123", "321"
]) ])
// use filters // use filters
await helloModuleService.deleteMyCustoms({ await blogModuleService.deletePosts({
selector: { selector: {
id: ["123", "321"] id: ["123", "321"]
} }
@@ -231,44 +231,44 @@ Find a complete reference of each of the methods in [this documentation](!resour
``` ```
</TabsContent> </TabsContent>
<TabsContent value="softDeleteMyCustoms"> <TabsContent value="softDeletePosts">
### softDeleteMyCustoms ### softDeletePosts
This method soft-deletes records using an array of IDs or an object of filters. This method soft-deletes records using an array of IDs or an object of filters.
For example: For example:
```ts ```ts
await helloModuleService.softDeleteMyCustoms("123") await blogModuleService.softDeletePosts("123")
// soft-delete multiple // soft-delete multiple
await helloModuleService.softDeleteMyCustoms([ await blogModuleService.softDeletePosts([
"123", "321" "123", "321"
]) ])
// use filters // use filters
await helloModuleService.softDeleteMyCustoms({ await blogModuleService.softDeletePosts({
id: ["123", "321"] id: ["123", "321"]
}) })
``` ```
</TabsContent> </TabsContent>
<TabsContent value="restoreMyCustoms"> <TabsContent value="restorePosts">
### restoreMyCustoms ### restorePosts
This method restores soft-deleted records using an array of IDs or an object of filters. This method restores soft-deleted records using an array of IDs or an object of filters.
For example: For example:
```ts ```ts
await helloModuleService.restoreMyCustoms([ await blogModuleService.restorePosts([
"123", "321" "123", "321"
]) ])
// use filters // use filters
await helloModuleService.restoreMyCustoms({ await blogModuleService.restorePosts({
id: ["123", "321"] id: ["123", "321"]
}) })
``` ```
@@ -285,15 +285,15 @@ For example:
```ts highlights={[["8"]]} ```ts highlights={[["8"]]}
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
constructor() { constructor() {
super(...arguments) super(...arguments)
} }
} }
export default HelloModuleService export default BlogModuleService
``` ```
+11 -11
View File
@@ -18,7 +18,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/modules/modules-directory-structure/page.mdx": "2024-12-09T10:32:46.839Z", "app/learn/fundamentals/modules/modules-directory-structure/page.mdx": "2024-12-09T10:32:46.839Z",
"app/learn/fundamentals/workflows/access-workflow-errors/page.mdx": "2024-10-21T13:30:21.371Z", "app/learn/fundamentals/workflows/access-workflow-errors/page.mdx": "2024-10-21T13:30:21.371Z",
"app/learn/fundamentals/events-and-subscribers/page.mdx": "2024-12-09T10:48:09.285Z", "app/learn/fundamentals/events-and-subscribers/page.mdx": "2024-12-09T10:48:09.285Z",
"app/learn/fundamentals/modules/container/page.mdx": "2024-11-21T09:22:27.898Z", "app/learn/fundamentals/modules/container/page.mdx": "2025-03-18T15:10:03.574Z",
"app/learn/fundamentals/workflows/execute-another-workflow/page.mdx": "2024-12-09T15:56:22.895Z", "app/learn/fundamentals/workflows/execute-another-workflow/page.mdx": "2024-12-09T15:56:22.895Z",
"app/learn/fundamentals/modules/loaders/page.mdx": "2024-12-09T10:32:29.221Z", "app/learn/fundamentals/modules/loaders/page.mdx": "2024-12-09T10:32:29.221Z",
"app/learn/fundamentals/admin/widgets/page.mdx": "2024-12-09T16:43:24.260Z", "app/learn/fundamentals/admin/widgets/page.mdx": "2024-12-09T16:43:24.260Z",
@@ -28,19 +28,19 @@ export const generatedEditDates = {
"app/learn/fundamentals/workflows/add-workflow-hook/page.mdx": "2024-12-09T14:42:39.693Z", "app/learn/fundamentals/workflows/add-workflow-hook/page.mdx": "2024-12-09T14:42:39.693Z",
"app/learn/fundamentals/events-and-subscribers/data-payload/page.mdx": "2024-10-21T13:30:21.369Z", "app/learn/fundamentals/events-and-subscribers/data-payload/page.mdx": "2024-10-21T13:30:21.369Z",
"app/learn/fundamentals/workflows/advanced-example/page.mdx": "2024-09-11T10:46:59.975Z", "app/learn/fundamentals/workflows/advanced-example/page.mdx": "2024-09-11T10:46:59.975Z",
"app/learn/fundamentals/events-and-subscribers/emit-event/page.mdx": "2024-11-25T16:19:32.168Z", "app/learn/fundamentals/events-and-subscribers/emit-event/page.mdx": "2025-03-18T15:09:40.243Z",
"app/learn/fundamentals/workflows/conditions/page.mdx": "2025-01-27T08:45:19.027Z", "app/learn/fundamentals/workflows/conditions/page.mdx": "2025-01-27T08:45:19.027Z",
"app/learn/fundamentals/modules/module-link-directions/page.mdx": "2024-07-24T09:16:01+02:00", "app/learn/fundamentals/modules/module-link-directions/page.mdx": "2024-07-24T09:16:01+02:00",
"app/learn/fundamentals/admin/page.mdx": "2024-10-23T07:08:55.898Z", "app/learn/fundamentals/admin/page.mdx": "2024-10-23T07:08:55.898Z",
"app/learn/fundamentals/workflows/long-running-workflow/page.mdx": "2025-03-18T08:02:14.085Z", "app/learn/fundamentals/workflows/long-running-workflow/page.mdx": "2025-03-18T08:02:14.085Z",
"app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2025-02-12T13:55:33.437Z", "app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2025-02-12T13:55:33.437Z",
"app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-03-18T08:00:44.980Z", "app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-03-18T08:00:44.980Z",
"app/learn/fundamentals/data-models/manage-relationships/page.mdx": "2025-02-11T15:53:12.541Z", "app/learn/fundamentals/data-models/manage-relationships/page.mdx": "2025-03-18T15:09:18.688Z",
"app/learn/fundamentals/modules/remote-query/page.mdx": "2024-07-21T21:20:24+02:00", "app/learn/fundamentals/modules/remote-query/page.mdx": "2024-07-21T21:20:24+02:00",
"app/learn/fundamentals/modules/options/page.mdx": "2025-02-12T16:00:28.484Z", "app/learn/fundamentals/modules/options/page.mdx": "2025-03-18T15:12:34.510Z",
"app/learn/fundamentals/data-models/relationships/page.mdx": "2025-03-18T07:52:07.421Z", "app/learn/fundamentals/data-models/relationships/page.mdx": "2025-03-18T07:52:07.421Z",
"app/learn/fundamentals/workflows/compensation-function/page.mdx": "2024-12-06T14:34:50.384Z", "app/learn/fundamentals/workflows/compensation-function/page.mdx": "2024-12-06T14:34:50.384Z",
"app/learn/fundamentals/modules/service-factory/page.mdx": "2024-10-21T13:30:21.371Z", "app/learn/fundamentals/modules/service-factory/page.mdx": "2025-03-18T15:14:13.486Z",
"app/learn/fundamentals/modules/module-links/page.mdx": "2024-09-30T08:43:53.126Z", "app/learn/fundamentals/modules/module-links/page.mdx": "2024-09-30T08:43:53.126Z",
"app/learn/fundamentals/scheduled-jobs/execution-number/page.mdx": "2024-10-21T13:30:21.371Z", "app/learn/fundamentals/scheduled-jobs/execution-number/page.mdx": "2024-10-21T13:30:21.371Z",
"app/learn/fundamentals/api-routes/parameters/page.mdx": "2025-02-14T08:34:03.184Z", "app/learn/fundamentals/api-routes/parameters/page.mdx": "2025-02-14T08:34:03.184Z",
@@ -52,25 +52,25 @@ export const generatedEditDates = {
"app/learn/fundamentals/modules/isolation/page.mdx": "2024-12-09T11:02:38.087Z", "app/learn/fundamentals/modules/isolation/page.mdx": "2024-12-09T11:02:38.087Z",
"app/learn/fundamentals/data-models/index/page.mdx": "2025-03-18T07:59:07.798Z", "app/learn/fundamentals/data-models/index/page.mdx": "2025-03-18T07:59:07.798Z",
"app/learn/fundamentals/custom-cli-scripts/page.mdx": "2024-10-23T07:08:55.898Z", "app/learn/fundamentals/custom-cli-scripts/page.mdx": "2024-10-23T07:08:55.898Z",
"app/learn/debugging-and-testing/testing-tools/integration-tests/api-routes/page.mdx": "2025-02-13T16:39:29.636Z", "app/learn/debugging-and-testing/testing-tools/integration-tests/api-routes/page.mdx": "2025-03-18T15:06:27.864Z",
"app/learn/debugging-and-testing/testing-tools/integration-tests/page.mdx": "2024-12-09T15:52:01.019Z", "app/learn/debugging-and-testing/testing-tools/integration-tests/page.mdx": "2024-12-09T15:52:01.019Z",
"app/learn/debugging-and-testing/testing-tools/integration-tests/workflows/page.mdx": "2025-02-11T15:56:03.835Z", "app/learn/debugging-and-testing/testing-tools/integration-tests/workflows/page.mdx": "2025-02-11T15:56:03.835Z",
"app/learn/debugging-and-testing/testing-tools/page.mdx": "2025-01-31T13:19:02.587Z", "app/learn/debugging-and-testing/testing-tools/page.mdx": "2025-01-31T13:19:02.587Z",
"app/learn/debugging-and-testing/testing-tools/unit-tests/module-example/page.mdx": "2024-09-02T11:04:27.232Z", "app/learn/debugging-and-testing/testing-tools/unit-tests/module-example/page.mdx": "2024-09-02T11:04:27.232Z",
"app/learn/debugging-and-testing/testing-tools/unit-tests/page.mdx": "2024-09-02T11:03:26.997Z", "app/learn/debugging-and-testing/testing-tools/unit-tests/page.mdx": "2024-09-02T11:03:26.997Z",
"app/learn/fundamentals/modules/service-constraints/page.mdx": "2024-11-19T16:37:47.253Z", "app/learn/fundamentals/modules/service-constraints/page.mdx": "2025-03-18T15:12:46.006Z",
"app/learn/fundamentals/api-routes/responses/page.mdx": "2024-10-21T13:30:21.367Z", "app/learn/fundamentals/api-routes/responses/page.mdx": "2024-10-21T13:30:21.367Z",
"app/learn/fundamentals/api-routes/validation/page.mdx": "2024-12-09T13:04:02.426Z", "app/learn/fundamentals/api-routes/validation/page.mdx": "2024-12-09T13:04:02.426Z",
"app/learn/fundamentals/api-routes/errors/page.mdx": "2024-12-09T16:44:19.781Z", "app/learn/fundamentals/api-routes/errors/page.mdx": "2024-12-09T16:44:19.781Z",
"app/learn/fundamentals/admin/constraints/page.mdx": "2024-10-21T13:30:21.366Z", "app/learn/fundamentals/admin/constraints/page.mdx": "2024-10-21T13:30:21.366Z",
"app/learn/debugging-and-testing/testing-tools/modules-tests/module-example/page.mdx": "2025-01-31T13:19:02.586Z", "app/learn/debugging-and-testing/testing-tools/modules-tests/module-example/page.mdx": "2025-03-18T15:07:22.640Z",
"app/learn/debugging-and-testing/testing-tools/modules-tests/page.mdx": "2025-01-31T13:19:02.587Z", "app/learn/debugging-and-testing/testing-tools/modules-tests/page.mdx": "2025-03-18T15:08:42.198Z",
"app/learn/fundamentals/module-links/custom-columns/page.mdx": "2025-03-11T13:29:54.752Z", "app/learn/fundamentals/module-links/custom-columns/page.mdx": "2025-03-11T13:29:54.752Z",
"app/learn/fundamentals/module-links/directions/page.mdx": "2025-03-17T12:52:06.161Z", "app/learn/fundamentals/module-links/directions/page.mdx": "2025-03-17T12:52:06.161Z",
"app/learn/fundamentals/module-links/page.mdx": "2025-03-11T13:39:14.345Z", "app/learn/fundamentals/module-links/page.mdx": "2025-03-11T13:39:14.345Z",
"app/learn/fundamentals/module-links/query/page.mdx": "2025-03-11T15:35:10.605Z", "app/learn/fundamentals/module-links/query/page.mdx": "2025-03-11T15:35:10.605Z",
"app/learn/fundamentals/modules/db-operations/page.mdx": "2024-12-09T14:40:50.581Z", "app/learn/fundamentals/modules/db-operations/page.mdx": "2025-03-18T15:10:57.351Z",
"app/learn/fundamentals/modules/multiple-services/page.mdx": "2024-10-21T13:30:21.370Z", "app/learn/fundamentals/modules/multiple-services/page.mdx": "2025-03-18T15:11:44.632Z",
"app/learn/fundamentals/modules/page.mdx": "2025-03-18T07:51:09.049Z", "app/learn/fundamentals/modules/page.mdx": "2025-03-18T07:51:09.049Z",
"app/learn/debugging-and-testing/instrumentation/page.mdx": "2025-02-24T08:12:53.132Z", "app/learn/debugging-and-testing/instrumentation/page.mdx": "2025-02-24T08:12:53.132Z",
"app/learn/fundamentals/api-routes/additional-data/page.mdx": "2025-01-27T08:45:19.025Z", "app/learn/fundamentals/api-routes/additional-data/page.mdx": "2025-01-27T08:45:19.025Z",
File diff suppressed because it is too large. Load diff
+160 -160
View File
@@ -713,44 +713,44 @@ Find this example explained in details in [this documentation](!docs!/learn/fund
</Note> </Note>
1. Create the directory `src/modules/hello`. 1. Create the directory `src/modules/blog`.
2. Create the file `src/modules/hello/models/my-custom.ts` with the following data model: 2. Create the file `src/modules/blog/models/post.ts` with the following data model:
```ts title="src/modules/hello/models/my-custom.ts" ```ts title="src/modules/blog/models/post.ts"
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
id: model.id().primaryKey(), id: model.id().primaryKey(),
name: model.text(), title: model.text(),
}) })
export default MyCustom export default Post
``` ```
3. Create the file `src/modules/hello/service.ts` with the following service: 3. Create the file `src/modules/blog/service.ts` with the following service:
```ts title="src/modules/hello/service.ts" ```ts title="src/modules/blog/service.ts"
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
} }
export default HelloModuleService export default BlogModuleService
``` ```
4. Create the file `src/modules/hello/index.ts` that exports the module definition: 4. Create the file `src/modules/blog/index.ts` that exports the module definition:
```ts title="src/modules/hello/index.ts" ```ts title="src/modules/blog/index.ts"
import HelloModuleService from "./service" import BlogModuleService from "./service"
import { Module } from "@medusajs/framework/utils" import { Module } from "@medusajs/framework/utils"
export const HELLO_MODULE = "helloModuleService" export const BLOG_MODULE = "blog"
export default Module(HELLO_MODULE, { export default Module(BLOG_MODULE, {
service: HelloModuleService, service: BlogModuleService,
}) })
``` ```
@@ -763,7 +763,7 @@ module.exports = defineConfig({
}, },
modules: [ modules: [
{ {
resolve: "./modules/hello", resolve: "./modules/blog",
}, },
], ],
}) })
@@ -772,7 +772,7 @@ module.exports = defineConfig({
6. Generate and run migrations: 6. Generate and run migrations:
```bash ```bash
npx medusa db:generate helloModuleService npx medusa db:generate blog
npx medusa db:migrate npx medusa db:migrate
``` ```
@@ -780,23 +780,23 @@ npx medusa db:migrate
```ts title="src/api/custom/route.ts" ```ts title="src/api/custom/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import HelloModuleService from "../../modules/hello/service" import BlogModuleService from "../../modules/blog/service"
import { HELLO_MODULE } from "../../modules/hello" import { BLOG_MODULE } from "../../modules/blog"
export async function GET( export async function GET(
req: MedusaRequest, req: MedusaRequest,
res: MedusaResponse res: MedusaResponse
): Promise<void> { ): Promise<void> {
const helloModuleService: HelloModuleService = req.scope.resolve( const blogModuleService: BlogModuleService = req.scope.resolve(
HELLO_MODULE BLOG_MODULE
) )
const my_custom = await helloModuleService.createMyCustoms({ const post = await blogModuleService.createPosts({
name: "test", title: "test",
}) })
res.json({ res.json({
my_custom, post,
}) })
} }
``` ```
@@ -805,44 +805,44 @@ export async function GET(
To add services in your module other than the main one, create them in the `services` directory of the module. To add services in your module other than the main one, create them in the `services` directory of the module.
For example, create the file `src/modules/hello/services/custom.ts` with the following content: For example, create the file `src/modules/blog/services/category.ts` with the following content:
```ts title="src/modules/hello/services/custom.ts" ```ts title="src/modules/blog/services/category.ts"
export class CustomService { export class CategoryService {
// TODO add methods // TODO add methods
} }
``` ```
Then, export the service in the file `src/modules/hello/services/index.ts`: Then, export the service in the file `src/modules/blog/services/index.ts`:
```ts title="src/modules/hello/services/index.ts" ```ts title="src/modules/blog/services/index.ts"
export * from "./custom" export * from "./category"
``` ```
Finally, resolve the service in your module's main service or loader: Finally, resolve the service in your module's main service or loader:
```ts title="src/modules/hello/service.ts" ```ts title="src/modules/blog/service.ts"
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
import { CustomService } from "./services" import { CategoryService } from "./services"
type InjectedDependencies = { type InjectedDependencies = {
customService: CustomService categoryService: CategoryService
} }
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
private customService: CustomService private categoryService: CategoryService
constructor({ customService }: InjectedDependencies) { constructor({ categoryService }: InjectedDependencies) {
super(...arguments) super(...arguments)
this.customService = customService this.categoryService = categoryService
} }
} }
export default HelloModuleService export default BlogModuleService
``` ```
Learn more in [this documentation](!docs!/learn/fundamentals/modules/multiple-services). Learn more in [this documentation](!docs!/learn/fundamentals/modules/multiple-services).
@@ -860,7 +860,7 @@ module.exports = defineConfig({
// ... // ...
modules: [ modules: [
{ {
resolve: "./modules/hello", resolve: "./modules/blog",
options: { options: {
apiKey: true, apiKey: true,
}, },
@@ -871,17 +871,17 @@ module.exports = defineConfig({
2. Access the options in the module's main service: 2. Access the options in the module's main service:
```ts title="src/modules/hello/service.ts" highlights={[["14", "options"]]} ```ts title="src/modules/blog/service.ts" highlights={[["14", "options"]]}
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
// recommended to define type in another file // recommended to define type in another file
type ModuleOptions = { type ModuleOptions = {
apiKey?: boolean apiKey?: boolean
} }
export default class HelloModuleService extends MedusaService({ export default class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
protected options_: ModuleOptions protected options_: ModuleOptions
@@ -903,9 +903,9 @@ Learn more in [this documentation](!docs!/learn/fundamentals/modules/options).
An example of integrating a dummy third-party system in a module's service: An example of integrating a dummy third-party system in a module's service:
```ts title="src/modules/hello/service.ts" ```ts title="src/modules/blog/service.ts"
import { Logger } from "@medusajs/framework/types" import { Logger } from "@medusajs/framework/types"
import { BRAND_MODULE } from ".." import { BLOG_MODULE } from ".."
export type ModuleOptions = { export type ModuleOptions = {
apiKey: string apiKey: string
@@ -915,7 +915,7 @@ type InjectedDependencies = {
logger: Logger logger: Logger
} }
export class BrandClient { export class BlogClient {
private options_: ModuleOptions private options_: ModuleOptions
private logger_: Logger private logger_: Logger
@@ -956,23 +956,23 @@ This assumes you already have a module. If not, follow [this example](#create-mo
</Note> </Note>
1. Create the file `src/modules/hello/models/my-custom.ts` with the following data model: 1. Create the file `src/modules/blog/models/post.ts` with the following data model:
```ts title="src/modules/hello/models/my-custom.ts" ```ts title="src/modules/blog/models/post.ts"
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
id: model.id().primaryKey(), id: model.id().primaryKey(),
name: model.text(), title: model.text(),
}) })
export default MyCustom export default Post
``` ```
2. Generate and run migrations: 2. Generate and run migrations:
```bash ```bash
npx medusa db:generate helloModuleService npx medusa db:generate blog
npx medusa db:migrate npx medusa db:migrate
``` ```
@@ -985,7 +985,7 @@ A data model can have properties of the following types:
1. ID property: 1. ID property:
```ts ```ts
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
id: model.id(), id: model.id(),
// ... // ...
}) })
@@ -994,8 +994,8 @@ const MyCustom = model.define("my_custom", {
2. Text property: 2. Text property:
```ts ```ts
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
name: model.text(), title: model.text(),
// ... // ...
}) })
``` ```
@@ -1003,8 +1003,8 @@ const MyCustom = model.define("my_custom", {
3. Number property: 3. Number property:
```ts ```ts
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
age: model.number(), views: model.number(),
// ... // ...
}) })
``` ```
@@ -1012,7 +1012,7 @@ const MyCustom = model.define("my_custom", {
4. Big Number property: 4. Big Number property:
```ts ```ts
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
price: model.bigNumber(), price: model.bigNumber(),
// ... // ...
}) })
@@ -1021,8 +1021,8 @@ const MyCustom = model.define("my_custom", {
5. Boolean property: 5. Boolean property:
```ts ```ts
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
hasAccount: model.boolean(), isPublished: model.boolean(),
// ... // ...
}) })
``` ```
@@ -1030,8 +1030,8 @@ const MyCustom = model.define("my_custom", {
6. Enum property: 6. Enum property:
```ts ```ts
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
color: model.enum(["black", "white"]), status: model.enum(["draft", "published"]),
// ... // ...
}) })
``` ```
@@ -1039,8 +1039,8 @@ const MyCustom = model.define("my_custom", {
7. Date-Time property: 7. Date-Time property:
```ts ```ts
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
date_of_birth: model.dateTime(), publishedAt: model.dateTime(),
// ... // ...
}) })
``` ```
@@ -1048,7 +1048,7 @@ const MyCustom = model.define("my_custom", {
8. JSON property: 8. JSON property:
```ts ```ts
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
metadata: model.json(), metadata: model.json(),
// ... // ...
}) })
@@ -1057,8 +1057,8 @@ const MyCustom = model.define("my_custom", {
9. Array property: 9. Array property:
```ts ```ts
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
names: model.array(), tags: model.array(),
// ... // ...
}) })
``` ```
@@ -1072,12 +1072,12 @@ To set an `id` property as the primary key of a data model:
```ts highlights={[["4", "primaryKey"]]} ```ts highlights={[["4", "primaryKey"]]}
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
id: model.id().primaryKey(), id: model.id().primaryKey(),
// ... // ...
}) })
export default MyCustom export default Post
``` ```
To set a `text` property as the primary key: To set a `text` property as the primary key:
@@ -1085,12 +1085,12 @@ To set a `text` property as the primary key:
```ts highlights={[["4", "primaryKey"]]} ```ts highlights={[["4", "primaryKey"]]}
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
name: model.text().primaryKey(), title: model.text().primaryKey(),
// ... // ...
}) })
export default MyCustom export default Post
``` ```
To set a `number` property as the primary key: To set a `number` property as the primary key:
@@ -1098,12 +1098,12 @@ To set a `number` property as the primary key:
```ts highlights={[["4", "primaryKey"]]} ```ts highlights={[["4", "primaryKey"]]}
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
age: model.number().primaryKey(), views: model.number().primaryKey(),
// ... // ...
}) })
export default MyCustom export default Post
``` ```
Learn more in [this documentation](!docs!/learn/fundamentals/data-models/properties#set-primary-key-property). Learn more in [this documentation](!docs!/learn/fundamentals/data-models/properties#set-primary-key-property).
@@ -1115,17 +1115,17 @@ To set the default value of a property:
```ts highlights={[["6"], ["9"]]} ```ts highlights={[["6"], ["9"]]}
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
color: model status: model
.enum(["black", "white"]) .enum(["draft", "published"])
.default("black"), .default("draft"),
age: model views: model
.number() .number()
.default(0), .default(0),
// ... // ...
}) })
export default MyCustom export default Post
``` ```
Learn more in [this documentation](!docs!/learn/fundamentals/data-models/properties#property-default-value). Learn more in [this documentation](!docs!/learn/fundamentals/data-models/properties#property-default-value).
@@ -1137,12 +1137,12 @@ To allow `null` values for a property:
```ts highlights={[["4", "nullable"]]} ```ts highlights={[["4", "nullable"]]}
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
price: model.bigNumber().nullable(), price: model.bigNumber().nullable(),
// ... // ...
}) })
export default MyCustom export default Post
``` ```
Learn more in [this documentation](!docs!/learn/fundamentals/data-models/properties#make-property-optional). Learn more in [this documentation](!docs!/learn/fundamentals/data-models/properties#make-property-optional).
@@ -1154,12 +1154,12 @@ To create a unique index on a property:
```ts highlights={[["4", "unique"]]} ```ts highlights={[["4", "unique"]]}
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const User = model.define("user", { const Post = model.define("post", {
email: model.text().unique(), title: model.text().unique(),
// ... // ...
}) })
export default User export default Post
``` ```
Learn more in [this documentation](!docs!/learn/fundamentals/data-models/properties#unique-property). Learn more in [this documentation](!docs!/learn/fundamentals/data-models/properties#unique-property).
@@ -1173,8 +1173,8 @@ import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const MyCustom = model.define("my_custom", {
id: model.id().primaryKey(), id: model.id().primaryKey(),
name: model.text().index( title: model.text().index(
"IDX_MY_CUSTOM_NAME" "IDX_POST_TITLE"
), ),
}) })
@@ -1217,24 +1217,24 @@ To make a property searchable using terms or keywords:
```ts highlights={[["4", "searchable"]]} ```ts highlights={[["4", "searchable"]]}
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", { const Post = model.define("post", {
name: model.text().searchable(), title: model.text().searchable(),
// ... // ...
}) })
export default MyCustom export default Post
``` ```
Then, to search by that property, pass the `q` filter to the `list` or `listAndCount` generated methods of the module's main service: Then, to search by that property, pass the `q` filter to the `list` or `listAndCount` generated methods of the module's main service:
<Note> <Note>
`helloModuleService` is the main service that the data models belong to. `blogModuleService` is the main service that manages the `Post` data model.
</Note> </Note>
```ts ```ts
const myCustoms = await helloModuleService.listMyCustoms({ const posts = await blogModuleService.listPosts({
q: "John", q: "John",
}) })
``` ```
@@ -1339,19 +1339,19 @@ To set the ID of the user that an email belongs to:
<Note> <Note>
`helloModuleService` is the main service that the data models belong to. `blogModuleService` is the main service that manages the `Email` and `User` data models.
</Note> </Note>
```ts ```ts
// when creating an email // when creating an email
const email = await helloModuleService.createEmails({ const email = await blogModuleService.createEmails({
// other properties... // other properties...
user: "123", user: "123",
}) })
// when updating an email // when updating an email
const email = await helloModuleService.updateEmails({ const email = await blogModuleService.updateEmails({
id: "321", id: "321",
// other properties... // other properties...
user: "123", user: "123",
@@ -1362,13 +1362,13 @@ And to set the ID of a user's email when creating or updating it:
```ts ```ts
// when creating a user // when creating a user
const user = await helloModuleService.createUsers({ const user = await blogModuleService.createUsers({
// other properties... // other properties...
email: "123", email: "123",
}) })
// when updating a user // when updating a user
const user = await helloModuleService.updateUsers({ const user = await blogModuleService.updateUsers({
id: "321", id: "321",
// other properties... // other properties...
email: "123", email: "123",
@@ -1385,19 +1385,19 @@ To set the ID of the store that a product belongs to:
<Note> <Note>
`helloModuleService` is the main service that the data models belong to. `blogModuleService` is the main service that manages the `Product` and `Store` data models.
</Note> </Note>
```ts ```ts
// when creating a product // when creating a product
const product = await helloModuleService.createProducts({ const product = await blogModuleService.createProducts({
// other properties... // other properties...
store_id: "123", store_id: "123",
}) })
// when updating a product // when updating a product
const product = await helloModuleService.updateProducts({ const product = await blogModuleService.updateProducts({
id: "321", id: "321",
// other properties... // other properties...
store_id: "123", store_id: "123",
@@ -1414,12 +1414,12 @@ To set the orders a product has when creating it:
<Note> <Note>
`helloModuleService` is the main service that the data models belong to. `blogModuleService` is the main service that manages the `Product` and `Order` data models.
</Note> </Note>
```ts ```ts
const product = await helloModuleService.createProducts({ const product = await blogModuleService.createProducts({
// other properties... // other properties...
orders: ["123", "321"], orders: ["123", "321"],
}) })
@@ -1428,14 +1428,14 @@ const product = await helloModuleService.createProducts({
To add new orders to a product without removing the previous associations: To add new orders to a product without removing the previous associations:
```ts ```ts
const product = await helloModuleService.retrieveProduct( const product = await blogModuleService.retrieveProduct(
"123", "123",
{ {
relations: ["orders"], relations: ["orders"],
} }
) )
const updatedProduct = await helloModuleService.updateProducts({ const updatedProduct = await blogModuleService.updateProducts({
id: product.id, id: product.id,
// other properties... // other properties...
orders: [ orders: [
@@ -1453,12 +1453,12 @@ To retrieve records related to a data model's records through a relation, pass t
<Note> <Note>
`helloModuleService` is the main service that the data models belong to. `blogModuleService` is the main service that manages the `Product` and `Order` data models.
</Note> </Note>
```ts highlights={[["4", "relations"]]} ```ts highlights={[["4", "relations"]]}
const product = await helloModuleService.retrieveProducts( const product = await blogModuleService.retrieveProducts(
"123", "123",
{ {
relations: ["orders"], relations: ["orders"],
@@ -1482,18 +1482,18 @@ To extend the service factory in your module's service:
```ts highlights={[["4", "MedusaService"]]} ```ts highlights={[["4", "MedusaService"]]}
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
// TODO implement custom methods // TODO implement custom methods
} }
export default HelloModuleService export default BlogModuleService
``` ```
The `HelloModuleService` will now have data-management methods for `MyCustom`. The `BlogModuleService` will now have data-management methods for `Post`.
Refer to [this reference](../service-factory-reference/page.mdx) for details on the generated methods. Refer to [this reference](../service-factory-reference/page.mdx) for details on the generated methods.
@@ -1509,14 +1509,14 @@ To resolve resources from the module's container in a service:
```ts highlights={[["14"]]} ```ts highlights={[["14"]]}
import { Logger } from "@medusajs/framework/types" import { Logger } from "@medusajs/framework/types"
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
type InjectedDependencies = { type InjectedDependencies = {
logger: Logger logger: Logger
} }
class HelloModuleService extends MedusaService({ class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
protected logger_: Logger protected logger_: Logger
@@ -1524,13 +1524,13 @@ class HelloModuleService extends MedusaService({
super(...arguments) super(...arguments)
this.logger_ = logger this.logger_ = logger
this.logger_.info("[HelloModuleService]: Hello World!") this.logger_.info("[BlogModuleService]: Hello World!")
} }
// ... // ...
} }
export default HelloModuleService export default BlogModuleService
``` ```
</CodeTab> </CodeTab>
@@ -1543,13 +1543,13 @@ type InjectedDependencies = {
logger: Logger logger: Logger
} }
export default class HelloModuleService { export default class BlogModuleService {
protected logger_: Logger protected logger_: Logger
constructor({ logger }: InjectedDependencies) { constructor({ logger }: InjectedDependencies) {
this.logger_ = logger this.logger_ = logger
this.logger_.info("[HelloModuleService]: Hello World!") this.logger_.info("[BlogModuleService]: Hello World!")
} }
// ... // ...
@@ -1567,15 +1567,15 @@ To access options passed to a module in its service:
```ts highlights={[["14", "options"]]} ```ts highlights={[["14", "options"]]}
import { MedusaService } from "@medusajs/framework/utils" import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom" import Post from "./models/post"
// recommended to define type in another file // recommended to define type in another file
type ModuleOptions = { type ModuleOptions = {
apiKey?: boolean apiKey?: boolean
} }
export default class HelloModuleService extends MedusaService({ export default class BlogModuleService extends MedusaService({
MyCustom, Post,
}){ }){
protected options_: ModuleOptions protected options_: ModuleOptions
@@ -1604,14 +1604,14 @@ import {
MedusaContext, MedusaContext,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectManager() @InjectManager()
async getCount( async getCount(
@MedusaContext() sharedContext?: Context<EntityManager> @MedusaContext() sharedContext?: Context<EntityManager>
): Promise<number> { ): Promise<number> {
return await sharedContext.manager.count("my_custom") return await sharedContext.manager.count("post")
} }
@InjectManager() @InjectManager()
@@ -1619,7 +1619,7 @@ class HelloModuleService {
@MedusaContext() sharedContext?: Context<EntityManager> @MedusaContext() sharedContext?: Context<EntityManager>
): Promise<number> { ): Promise<number> {
const data = await sharedContext.manager.execute( const data = await sharedContext.manager.execute(
"SELECT COUNT(*) as num FROM my_custom" "SELECT COUNT(*) as num FROM post"
) )
return parseInt(data[0].num) return parseInt(data[0].num)
@@ -1642,7 +1642,7 @@ import {
import { Context } from "@medusajs/framework/types" import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex" import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService { class BlogModuleService {
// ... // ...
@InjectTransactionManager() @InjectTransactionManager()
protected async update_( protected async update_(
@@ -1654,7 +1654,7 @@ class HelloModuleService {
): Promise<any> { ): Promise<any> {
const transactionManager = sharedContext.transactionManager const transactionManager = sharedContext.transactionManager
await transactionManager.nativeUpdate( await transactionManager.nativeUpdate(
"my_custom", "post",
{ {
id: input.id, id: input.id,
}, },
@@ -1665,7 +1665,7 @@ class HelloModuleService {
// retrieve again // retrieve again
const updatedRecord = await transactionManager.execute( const updatedRecord = await transactionManager.execute(
`SELECT * FROM my_custom WHERE id = '${input.id}'` `SELECT * FROM post WHERE id = '${input.id}'`
) )
return updatedRecord return updatedRecord
@@ -1696,16 +1696,16 @@ A module link forms an association between two data models of different modules,
To define a link between your custom module and a commerce module, such as the Product Module: To define a link between your custom module and a commerce module, such as the Product Module:
1. Create the file `src/links/hello-product.ts` with the following content: 1. Create the file `src/links/blog-product.ts` with the following content:
```ts title="src/links/hello-product.ts" ```ts title="src/links/blog-product.ts"
import HelloModule from "../modules/hello" import BlogModule from "../modules/blog"
import ProductModule from "@medusajs/medusa/product" import ProductModule from "@medusajs/medusa/product"
import { defineLink } from "@medusajs/framework/utils" import { defineLink } from "@medusajs/framework/utils"
export default defineLink( export default defineLink(
ProductModule.linkable.product, ProductModule.linkable.product,
HelloModule.linkable.myCustom BlogModule.linkable.post
) )
``` ```
@@ -1722,14 +1722,14 @@ Learn more in [this documentation](!docs!/learn/fundamentals/module-links).
To define a list link, where multiple records of a model can be linked to a record in another: To define a list link, where multiple records of a model can be linked to a record in another:
```ts highlights={[["9", "isList"]]} ```ts highlights={[["9", "isList"]]}
import HelloModule from "../modules/hello" import BlogModule from "../modules/blog"
import ProductModule from "@medusajs/medusa/product" import ProductModule from "@medusajs/medusa/product"
import { defineLink } from "@medusajs/framework/utils" import { defineLink } from "@medusajs/framework/utils"
export default defineLink( export default defineLink(
ProductModule.linkable.product, ProductModule.linkable.product,
{ {
linkable: HelloModule.linkable.myCustom, linkable: BlogModule.linkable.post,
isList: true, isList: true,
} }
) )
@@ -1742,14 +1742,14 @@ Learn more about list links in [this documentation](!docs!/learn/fundamentals/mo
To ensure a model's records linked to another model are deleted when the linked model is deleted: To ensure a model's records linked to another model are deleted when the linked model is deleted:
```ts highlights={[["9", "deleteCascades"]]} ```ts highlights={[["9", "deleteCascades"]]}
import HelloModule from "../modules/hello" import BlogModule from "../modules/blog"
import ProductModule from "@medusajs/medusa/product" import ProductModule from "@medusajs/medusa/product"
import { defineLink } from "@medusajs/framework/utils" import { defineLink } from "@medusajs/framework/utils"
export default defineLink( export default defineLink(
ProductModule.linkable.product, ProductModule.linkable.product,
{ {
linkable: HelloModule.linkable.myCustom, linkable: BlogModule.linkable.post,
deleteCascades: true, deleteCascades: true,
} }
) )
@@ -1762,13 +1762,13 @@ Learn more in [this documentation](!docs!/learn/fundamentals/module-links#define
To add a custom column to the table that stores the linked records of two data models: To add a custom column to the table that stores the linked records of two data models:
```ts highlights={[["9", "database"]]} ```ts highlights={[["9", "database"]]}
import HelloModule from "../modules/hello" import BlogModule from "../modules/blog"
import ProductModule from "@medusajs/medusa/product" import ProductModule from "@medusajs/medusa/product"
import { defineLink } from "@medusajs/framework/utils" import { defineLink } from "@medusajs/framework/utils"
export default defineLink( export default defineLink(
ProductModule.linkable.product, ProductModule.linkable.product,
HelloModule.linkable.myCustom, BlogModule.linkable.post,
{ {
database: { database: {
extraColumns: { extraColumns: {
@@ -1802,13 +1802,13 @@ await link.create({
To retrieve the custom column when retrieving linked records using Query: To retrieve the custom column when retrieving linked records using Query:
```ts ```ts
import productHelloLink from "../links/product-hello" import productBlogLink from "../links/product-blog"
// ... // ...
const { data } = await query.graph({ const { data } = await query.graph({
entity: productHelloLink.entryPoint, entity: productBlogLink.entryPoint,
fields: ["metadata", "product.*", "my_custom.*"], fields: ["metadata", "product.*", "post.*"],
filters: { filters: {
product_id: "prod_123", product_id: "prod_123",
}, },
@@ -1823,7 +1823,7 @@ To create a link between two records using Link:
```ts ```ts
import { Modules } from "@medusajs/framework/utils" import { Modules } from "@medusajs/framework/utils"
import { HELLO_MODULE } from "../../modules/hello" import { BLOG_MODULE } from "../../modules/blog"
// ... // ...
@@ -1845,7 +1845,7 @@ To dismiss links between records using Link:
```ts ```ts
import { Modules } from "@medusajs/framework/utils" import { Modules } from "@medusajs/framework/utils"
import { HELLO_MODULE } from "../../modules/hello" import { BLOG_MODULE } from "../../modules/blog"
// ... // ...
@@ -1853,8 +1853,8 @@ await link.dismiss({
[Modules.PRODUCT]: { [Modules.PRODUCT]: {
product_id: "prod_123", product_id: "prod_123",
}, },
[HELLO_MODULE]: { [BLOG_MODULE]: {
my_custom_id: "mc_123", post_id: "mc_123",
}, },
}) })
``` ```
@@ -3307,20 +3307,20 @@ Learn more in [this documentation](!docs!/learn/debugging-and-testing/testing-to
To create a test for a module's service, create the test under the `__tests__` directory of the module. To create a test for a module's service, create the test under the `__tests__` directory of the module.
For example, create the file `src/modules/hello/__tests__/service.spec.ts` with the following content: For example, create the file `src/modules/blog/__tests__/service.spec.ts` with the following content:
```ts title="src/modules/hello/__tests__/service.spec.ts" ```ts title="src/modules/blog/__tests__/service.spec.ts"
import { moduleIntegrationTestRunner } from "@medusajs/test-utils" import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import { HELLO_MODULE } from ".." import { BLOG_MODULE } from ".."
import HelloModuleService from "../service" import BlogModuleService from "../service"
import MyCustom from "../models/my-custom" import Post from "../models/post"
moduleIntegrationTestRunner<HelloModuleService>({ moduleIntegrationTestRunner<BlogModuleService>({
moduleName: HELLO_MODULE, moduleName: BLOG_MODULE,
moduleModels: [MyCustom], moduleModels: [Post],
resolve: "./modules/hello", resolve: "./modules/blog",
testSuite: ({ service }) => { testSuite: ({ service }) => {
describe("HelloModuleService", () => { describe("BlogModuleService", () => {
it("says hello world", () => { it("says hello world", () => {
const message = service.getMessage() const message = service.getMessage()
@@ -411,13 +411,15 @@ The workflow’s steps are:
type: "step", type: "step",
name: "createVendorStep", name: "createVendorStep",
description: "Create the vendor.", description: "Create the vendor.",
depth: 1 depth: 1,
link: "#createvendorstep"
}, },
{ {
type: "step", type: "step",
name: "createVendorAdminStep", name: "createVendorAdminStep",
description: "Create the vendor admin.", description: "Create the vendor admin.",
depth: 1 depth: 1,
link: "#createvendoradminstep"
}, },
{ {
type: "step", type: "step",
@@ -910,7 +912,7 @@ The workflow to create a product has the following steps:
<WorkflowDiagram <WorkflowDiagram
workflow={{ workflow={{
nam: "createVendorProductWorkflow", name: "createVendorProductWorkflow",
steps: [ steps: [
{ {
type: "step", type: "step",
@@ -1280,7 +1282,8 @@ In this step, you’ll create a workflow that’s executed when the customer pla
type: "step", type: "step",
name: "groupVendorItemsStep", name: "groupVendorItemsStep",
description: "Group the items by their vendor.", description: "Group the items by their vendor.",
depth: 1 depth: 1,
link: "#groupvendoritemssstep"
}, },
{ {
type: "step", type: "step",
@@ -1293,7 +1296,8 @@ In this step, you’ll create a workflow that’s executed when the customer pla
type: "step", type: "step",
name: "createVendorOrdersStep", name: "createVendorOrdersStep",
description: "Create child orders for each vendor.", description: "Create child orders for each vendor.",
depth: 1 depth: 1,
link: "#createvendorordersstep"
}, },
{ {
type: "step", type: "step",
@@ -12,14 +12,14 @@ This document provides a reference to the `moduleIntegrationTestRunner` function
```ts ```ts
import { moduleIntegrationTestRunner } from "@medusajs/test-utils" import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import { HELLO_MODULE } from ".." import { BLOG_MODULE } from ".."
import HelloModuleService from "../service" import BlogModuleService from "../service"
import MyCustom from "../models/my-custom" import Post from "../models/post"
moduleIntegrationTestRunner<HelloModuleService>({ moduleIntegrationTestRunner<BlogModuleService>({
moduleName: HELLO_MODULE, moduleName: BLOG_MODULE,
moduleModels: [MyCustom], moduleModels: [Post],
resolve: "./src/modules/hello", resolve: "./src/modules/blog",
testSuite: ({ service }) => { testSuite: ({ service }) => {
// TODO write tests // TODO write tests
}, },
+3 -3
View File
@@ -114,7 +114,7 @@ export const generatedEditDates = {
"app/recipes/digital-products/examples/standard/page.mdx": "2025-02-13T15:24:15.868Z", "app/recipes/digital-products/examples/standard/page.mdx": "2025-02-13T15:24:15.868Z",
"app/recipes/digital-products/page.mdx": "2025-02-26T12:37:12.721Z", "app/recipes/digital-products/page.mdx": "2025-02-26T12:37:12.721Z",
"app/recipes/ecommerce/page.mdx": "2025-02-26T12:20:52.092Z", "app/recipes/ecommerce/page.mdx": "2025-02-26T12:20:52.092Z",
"app/recipes/marketplace/examples/vendors/page.mdx": "2025-03-17T10:10:58.268Z", "app/recipes/marketplace/examples/vendors/page.mdx": "2025-03-18T15:28:32.122Z",
"app/recipes/marketplace/page.mdx": "2024-10-03T13:07:44.153Z", "app/recipes/marketplace/page.mdx": "2024-10-03T13:07:44.153Z",
"app/recipes/multi-region-store/page.mdx": "2025-02-26T12:38:50.292Z", "app/recipes/multi-region-store/page.mdx": "2025-02-26T12:38:50.292Z",
"app/recipes/omnichannel/page.mdx": "2025-02-26T12:22:08.331Z", "app/recipes/omnichannel/page.mdx": "2025-02-26T12:22:08.331Z",
@@ -697,7 +697,7 @@ export const generatedEditDates = {
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminWorkflowExecutionResponse/page.mdx": "2024-12-09T13:21:34.761Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminWorkflowExecutionResponse/page.mdx": "2024-12-09T13:21:34.761Z",
"references/types/interfaces/types.BaseReturnItem/page.mdx": "2024-12-09T13:21:35.009Z", "references/types/interfaces/types.BaseReturnItem/page.mdx": "2024-12-09T13:21:35.009Z",
"app/test-tools-reference/medusaIntegrationTestRunner/page.mdx": "2024-10-16T15:47:38.579Z", "app/test-tools-reference/medusaIntegrationTestRunner/page.mdx": "2024-10-16T15:47:38.579Z",
"app/test-tools-reference/moduleIntegrationTestRunner/page.mdx": "2024-10-16T15:47:38.504Z", "app/test-tools-reference/moduleIntegrationTestRunner/page.mdx": "2025-03-18T15:19:56.773Z",
"app/test-tools-reference/page.mdx": "2024-12-09T16:23:04.825Z", "app/test-tools-reference/page.mdx": "2024-12-09T16:23:04.825Z",
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminGetInvitesParams/page.mdx": "2025-01-13T18:05:54.493Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminGetInvitesParams/page.mdx": "2025-01-13T18:05:54.493Z",
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminInventoryLevel/page.mdx": "2024-12-23T13:57:05.362Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminInventoryLevel/page.mdx": "2024-12-23T13:57:05.362Z",
@@ -2173,7 +2173,7 @@ export const generatedEditDates = {
"app/commerce-modules/sales-channel/links-to-other-modules/page.mdx": "2025-03-14T15:09:08.416Z", "app/commerce-modules/sales-channel/links-to-other-modules/page.mdx": "2025-03-14T15:09:08.416Z",
"app/commerce-modules/stock-location/links-to-other-modules/page.mdx": "2025-03-14T15:04:43.112Z", "app/commerce-modules/stock-location/links-to-other-modules/page.mdx": "2025-03-14T15:04:43.112Z",
"app/commerce-modules/store/links-to-other-modules/page.mdx": "2025-03-17T06:52:04.187Z", "app/commerce-modules/store/links-to-other-modules/page.mdx": "2025-03-17T06:52:04.187Z",
"app/examples/page.mdx": "2025-02-04T07:36:39.956Z", "app/examples/page.mdx": "2025-03-18T15:19:46.808Z",
"app/medusa-cli/commands/build/page.mdx": "2024-11-11T11:00:49.665Z", "app/medusa-cli/commands/build/page.mdx": "2024-11-11T11:00:49.665Z",
"app/js-sdk/page.mdx": "2025-02-05T09:12:11.479Z", "app/js-sdk/page.mdx": "2025-02-05T09:12:11.479Z",
"references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.apiKey/page.mdx": "2025-01-13T18:05:58.463Z", "references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.apiKey/page.mdx": "2025-01-13T18:05:58.463Z",
@@ -1,14 +1,50 @@
import React from "react" import React from "react"
import { InlineCode } from "../../../InlineCode" import { InlineCode } from "../../../InlineCode"
import { Text } from "@medusajs/ui" import { Text } from "@medusajs/ui"
import { Bolt, InformationCircle } from "@medusajs/icons" import { Bolt, CursorArrowRays, InformationCircle } from "@medusajs/icons"
export const WorkflowDiagramLegend = () => { type WorkflowDiagramLegendProps = {
hideLegend?: boolean
}
export const WorkflowDiagramLegend = ({
hideLegend = false,
}: WorkflowDiagramLegendProps) => {
return ( return (
<div className="flex gap-docs_0.5 mt-1"> <div className="flex gap-docs_0.5 mt-1">
{!hideLegend && (
<>
<div className="flex items-center gap-docs_0.5">
<div className="flex size-[20px] items-center justify-center text-medusa-tag-orange-icon">
<Bolt />
</div>
<Text
size="xsmall"
leading="compact"
weight="plus"
className="select-none"
>
Workflow hook
</Text>
</div>
<div className="flex items-center gap-docs_0.5">
<div className="flex size-[20px] items-center justify-center text-medusa-tag-green-icon">
<InformationCircle />
</div>
<Text
size="xsmall"
leading="compact"
weight="plus"
className="select-none"
>
Step conditioned by <InlineCode>when</InlineCode>
</Text>
</div>
</>
)}
<div className="flex items-center gap-docs_0.5"> <div className="flex items-center gap-docs_0.5">
<div className="flex size-[20px] items-center justify-center text-medusa-tag-orange-icon"> <div className="flex size-[20px] items-center justify-center">
<Bolt /> <CursorArrowRays />
</div> </div>
<Text <Text
size="xsmall" size="xsmall"
@@ -16,20 +52,7 @@ export const WorkflowDiagramLegend = () => {
weight="plus" weight="plus"
className="select-none" className="select-none"
> >
Workflow Hook View step details
</Text>
</div>
<div className="flex items-center gap-docs_0.5">
<div className="flex size-[20px] items-center justify-center text-medusa-tag-green-icon">
<InformationCircle />
</div>
<Text
size="xsmall"
leading="compact"
weight="plus"
className="select-none"
>
Step conditioned by <InlineCode>when</InlineCode>
</Text> </Text>
</div> </div>
</div> </div>
@@ -21,7 +21,7 @@ export const WorkflowDiagramList = ({
<WorkflowDiagramListDepth cluster={cluster} next={next} key={depth} /> <WorkflowDiagramListDepth cluster={cluster} next={next} key={depth} />
) )
})} })}
{!hideLegend && <WorkflowDiagramLegend />} <WorkflowDiagramLegend hideLegend={hideLegend} />
</div> </div>
) )
} }