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

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

View File

@@ -101,24 +101,25 @@ jest.setTimeout(60 * 1000)
## 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"
const MyCustom = model.define("my_custom", {
const Post = model.define("post", {
id: model.id().primaryKey(),
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:
```ts title="src/api/custom/route.ts"
// 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,
res: MedusaResponse
) {
const helloModuleService: HelloModuleService = req.scope.resolve(
"helloModuleService"
const blogModuleService: BlogModuleService = req.scope.resolve(
BLOG_MODULE
)
const myCustom = await helloModuleService.createMyCustoms(
const post = await blogModuleService.createPosts(
req.body
)
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`:
@@ -150,7 +151,7 @@ export const postHighlights = [
```ts title="integration-tests/http/custom-routes.spec.ts" highlights={postHighlights}
// other imports...
import HelloModuleService from "../../src/modules/hello/service"
import BlogModuleService from "../../src/modules/blog/service"
medusaIntegrationTestRunner({
testSuite: ({ api, getContainer }) => {
@@ -171,8 +172,8 @@ medusaIntegrationTestRunner({
)
expect(response.status).toEqual(200)
expect(response.data).toHaveProperty("my_custom")
expect(response.data.my_custom).toEqual({
expect(response.data).toHaveProperty("post")
expect(response.data.post).toEqual({
id,
name: "Test",
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:
- 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.
### 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"
// other imports...
import HelloModuleService from "../../src/modules/hello/service"
import BlogModuleService from "../../src/modules/blog/service"
import { BLOG_MODULE } from "../../src/modules/blog"
medusaIntegrationTestRunner({
testSuite: ({ api, getContainer }) => {
@@ -213,11 +215,11 @@ medusaIntegrationTestRunner({
describe("POST /custom", () => {
// ...
afterAll(() => async () => {
const helloModuleService: HelloModuleService = getContainer().resolve(
"helloModuleService"
const blogModuleService: BlogModuleService = getContainer().resolve(
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"
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(
req: MedusaRequest,
res: MedusaResponse
) {
const helloModuleService: HelloModuleService = req.scope.resolve(
"helloModuleService"
const blogModuleService: BlogModuleService = req.scope.resolve(
BLOG_MODULE
)
await helloModuleService.deleteMyCustoms(req.params.id)
await blogModuleService.deletePosts(req.params.id)
res.json({
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`:
@@ -271,11 +274,11 @@ medusaIntegrationTestRunner({
const id = "1"
beforeAll(() => async () => {
const helloModuleService: HelloModuleService = getContainer().resolve(
"helloModuleService"
const blogModuleService: BlogModuleService = getContainer().resolve(
BLOG_MODULE
)
await helloModuleService.createMyCustoms({
await blogModuleService.createPosts({
id,
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:

View File

@@ -19,13 +19,13 @@ In this chapter, find an example of writing an integration test for a module usi
## 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 MyCustom from "./models/my-custom"
class HelloModuleService extends MedusaService({
class BlogModuleService extends MedusaService({
MyCustom,
}){
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 { HELLO_MODULE } from ".."
import HelloModuleService from "../service"
import { BLOG_MODULE } from ".."
import BlogModuleService from "../service"
import MyCustom from "../models/my-custom"
moduleIntegrationTestRunner<HelloModuleService>({
moduleName: HELLO_MODULE,
moduleIntegrationTestRunner<BlogModuleService>({
moduleName: BLOG_MODULE,
moduleModels: [MyCustom],
resolve: "./src/modules/hello",
resolve: "./src/modules/blog",
testSuite: ({ service }) => {
describe("HelloModuleService", () => {
describe("BlogModuleService", () => {
it("says hello world", () => {
const message = service.getMessage()
@@ -62,7 +62,7 @@ moduleIntegrationTestRunner<HelloModuleService>({
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.
---

View File

@@ -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.
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 { HELLO_MODULE } from ".."
import HelloModuleService from "../service"
import MyCustom from "../models/my-custom"
import { BLOG_MODULE } from ".."
import BlogModuleService from "../service"
import Post from "../models/post"
moduleIntegrationTestRunner<HelloModuleService>({
moduleName: HELLO_MODULE,
moduleModels: [MyCustom],
resolve: "./src/modules/hello",
moduleIntegrationTestRunner<BlogModuleService>({
moduleName: BLOG_MODULE,
moduleModels: [Post],
resolve: "./src/modules/blog",
testSuite: ({ service }) => {
// TODO write tests
},
@@ -86,9 +86,9 @@ For example:
```ts
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import HelloModuleService from "../service"
import BlogModuleService from "../service"
moduleIntegrationTestRunner<HelloModuleService>({
moduleIntegrationTestRunner<BlogModuleService>({
moduleOptions: {
apiKey: "123",
},
@@ -106,14 +106,14 @@ For example:
```ts
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import HelloModuleService from "../service"
import BlogModuleService from "../service"
import { model } from "@medusajs/framework/utils"
const DummyModel = model.define("dummy_model", {
id: model.id().primaryKey(),
})
moduleIntegrationTestRunner<HelloModuleService>({
moduleIntegrationTestRunner<BlogModuleService>({
moduleModels: [DummyModel],
// ...
})

View File

@@ -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:
```ts highlights={["4"]}
class HelloModuleService extends MedusaService({
class BlogModuleService extends MedusaService({
Order,
Product,
OrderProduct,
@@ -208,7 +208,7 @@ For example:
```ts
// create order-product association
const orderProduct = await helloModuleService.createOrderProducts({
const orderProduct = await blogModuleService.createOrderProducts({
order_id: "123",
product_id: "123",
metadata: {
@@ -217,7 +217,7 @@ const orderProduct = await helloModuleService.createOrderProducts({
})
// update order-product association
const orderProduct = await helloModuleService.updateOrderProducts({
const orderProduct = await blogModuleService.updateOrderProducts({
id: "123",
metadata: {
test: false,
@@ -225,7 +225,7 @@ const orderProduct = await helloModuleService.updateOrderProducts({
})
// 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`.
@@ -247,7 +247,7 @@ export const retrieveHighlights = [
]
```ts highlights={retrieveHighlights}
const product = await helloModuleService.retrieveProducts(
const product = await blogModuleService.retrieveProducts(
"123",
{
relations: ["orders"],

View File

@@ -96,12 +96,12 @@ To emit a service event:
<CodeTabs group="service_type">
<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 { MedusaService } from "@medusajs/framework/utils"
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
Post,
}){
protected eventBusService_: AbstractEventBusModuleService
@@ -115,10 +115,10 @@ class HelloModuleService extends MedusaService({
</CodeTab>
<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"
class HelloModuleService {
class BlogModuleService {
protected eventBusService_: AbstractEventBusModuleService
constructor({ event_bus }) {
@@ -138,8 +138,8 @@ export const serviceHighlights = [
["8", "data", "The data payload to pass with the event."]
]
```ts title="src/modules/hello/service.ts" highlights={serviceHighlights}
class HelloModuleService {
```ts title="src/modules/blog/service.ts" highlights={serviceHighlights}
class BlogModuleService {
// ...
performAction() {
// TODO perform action
@@ -173,7 +173,7 @@ module.exports = defineConfig({
// ...
modules: [
{
resolve: "./src/modules/hello",
resolve: "./src/modules/blog",
dependencies: [
Modules.EVENT_BUS,
],

View File

@@ -31,13 +31,13 @@ type InjectedDependencies = {
logger: Logger
}
export default class HelloModuleService {
export default class BlogModuleService {
protected logger_: Logger
constructor({ logger }: InjectedDependencies) {
this.logger_ = logger
this.logger_.info("[HelloModuleService]: Hello World!")
this.logger_.info("[BlogModuleService]: Hello World!")
}
// ...

View File

@@ -40,7 +40,7 @@ import {
} from "@medusajs/framework/utils"
import { SqlEntityManager } from "@mikro-orm/knex"
class HelloModuleService {
class BlogModuleService {
// ...
@InjectManager()
@@ -105,7 +105,7 @@ import {
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
class BlogModuleService {
// ...
@InjectTransactionManager()
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 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...
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
class BlogModuleService {
// ...
@InjectManager()
async update(
@@ -210,7 +210,7 @@ For example:
// other imports...
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
class BlogModuleService {
// ...
@InjectTransactionManager()
protected async anotherMethod(
@@ -253,15 +253,15 @@ For example, resolve the `baseRepository` in your service's constructor:
```ts highlights={[["14"]]}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
import Post from "./models/post"
import { DAL } from "@medusajs/framework/types"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
}
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
Post,
}){
protected baseRepository_: DAL.RepositoryService
@@ -271,7 +271,7 @@ class HelloModuleService extends MedusaService({
}
}
export default HelloModuleService
export default BlogModuleService
```
</CodeTab>
<CodeTab label="Without Service Factory" value="no-service-factory">
@@ -283,7 +283,7 @@ type InjectedDependencies = {
baseRepository: DAL.RepositoryService
}
class HelloModuleService {
class BlogModuleService {
protected baseRepository_: DAL.RepositoryService
constructor({ baseRepository }: InjectedDependencies) {
@@ -291,7 +291,7 @@ class HelloModuleService {
}
}
export default HelloModuleService
export default BlogModuleService
```
</CodeTab>
@@ -313,7 +313,7 @@ import {
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
class BlogModuleService {
// ...
@InjectTransactionManager()
protected async update_(
@@ -383,7 +383,7 @@ The second parameter of the `baseRepository_.transaction` method is an object of
// other imports...
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
class BlogModuleService {
// ...
@InjectTransactionManager()
async update_(
@@ -416,7 +416,7 @@ class HelloModuleService {
// other imports...
import { IsolationLevel } from "@mikro-orm/core"
class HelloModuleService {
class BlogModuleService {
// ...
@InjectTransactionManager()
async update_(
@@ -442,7 +442,7 @@ class HelloModuleService {
- If `transaction` is provided and this is disabled, the manager in `transaction` is re-used.
```ts highlights={[["16"]]}
class HelloModuleService {
class BlogModuleService {
// ...
@InjectTransactionManager()
async update_(

View File

@@ -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.
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 {
async getMessage(): Promise<string> {
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.
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"
```
@@ -48,7 +48,7 @@ Internal services exported in the `services/index.ts` file of your module are no
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...
import { ClientService } from "./services"
@@ -56,8 +56,8 @@ type InjectedDependencies = {
clientService: ClientService
}
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
Post,
}){
protected clientService_: ClientService
@@ -106,7 +106,7 @@ For example:
```ts
import { ConfigModule } from "@medusajs/framework/types"
import { HELLO_MODULE } from ".."
import { BLOG_MODULE } from ".."
export type InjectedDependencies = {
configModule: ConfigModule
@@ -116,7 +116,7 @@ export class ClientService {
protected options: Record<string, any>
constructor({ configModule }: InjectedDependencies) {
const moduleDef = configModule.modules[HELLO_MODULE]
const moduleDef = configModule.modules[BLOG_MODULE]
if (typeof moduleDef !== "boolean") {
this.options = moduleDef.options

View File

@@ -23,7 +23,7 @@ module.exports = defineConfig({
// ...
modules: [
{
resolve: "./src/modules/hello",
resolve: "./src/modules/blog",
options: {
capitalize: true,
},
@@ -64,17 +64,17 @@ The modules main service receives the module options as a second parameter.
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 MyCustom from "./models/my-custom"
import Post from "./models/post"
// recommended to define type in another file
type ModuleOptions = {
capitalize?: boolean
}
export default class HelloModuleService extends MedusaService({
MyCustom,
export default class BlogModuleService extends MedusaService({
Post,
}){
protected options_: ModuleOptions
@@ -98,7 +98,7 @@ The object that a modules loaders receive as a parameter has an `options` pro
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 {
LoaderOptions,
} from "@medusajs/framework/types"
@@ -113,7 +113,7 @@ export default async function helloWorldLoader({
}: LoaderOptions<ModuleOptions>) {
console.log(
"[HELLO MODULE] Just started the Medusa application!",
"[BLOG MODULE] Just started the Medusa application!",
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`:
```ts title="src/modules/hello/loaders/validate.ts"
```ts title="src/modules/blog/loaders/validate.ts"
import { LoaderOptions } from "@medusajs/framework/types"
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):
```ts title="src/modules/hello/index.ts"
```ts title="src/modules/blog/index.ts"
// other imports...
import validationLoader from "./loaders/validate"
export default Module("hello", {
export const BLOG_MODULE = "blog"
export default Module(BLOG_MODULE, {
// ...
loaders: [validationLoader],
})

View File

@@ -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:
```ts
await helloModuleService.getMessage()
await blogModuleService.getMessage()
```
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."]]}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
import Post from "./models/post"
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
Post,
}){
// Don't
getMessage(): string {
@@ -36,5 +36,5 @@ class HelloModuleService extends MedusaService({
}
}
export default HelloModuleService
export default BlogModuleService
```

View File

@@ -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.
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 = [
["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 MyCustom from "./models/my-custom"
import Post from "./models/post"
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
Post,
}){
// TODO implement custom methods
}
export default HelloModuleService
export default BlogModuleService
```
### MedusaService Parameters
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
@@ -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">
<TabsList>
<TabsTriggerVertical value="listMyCustoms">listMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="listAndCountMyCustoms">listAndCount</TabsTriggerVertical>
<TabsTriggerVertical value="retrieveMyCustom">retrieveMyCustom</TabsTriggerVertical>
<TabsTriggerVertical value="createMyCustoms">createMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="updateMyCustoms">updateMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="deleteMyCustoms">deleteMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="softDeleteMyCustoms">softDeleteMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="restoreMyCustoms">restoreMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="listPosts">listPosts</TabsTriggerVertical>
<TabsTriggerVertical value="listAndCountPosts">listAndCountPosts</TabsTriggerVertical>
<TabsTriggerVertical value="retrievePost">retrievePost</TabsTriggerVertical>
<TabsTriggerVertical value="createPosts">createPosts</TabsTriggerVertical>
<TabsTriggerVertical value="updatePosts">updatePosts</TabsTriggerVertical>
<TabsTriggerVertical value="deletePosts">deletePosts</TabsTriggerVertical>
<TabsTriggerVertical value="softDeletePosts">softDeletePosts</TabsTriggerVertical>
<TabsTriggerVertical value="restorePosts">restorePosts</TabsTriggerVertical>
</TabsList>
<TabsContentWrapper className="[&_h3]:!mt-0">
<TabsContent value="listMyCustoms">
### listMyCustoms
### listPosts
This method retrieves an array of records based on filters and pagination configurations.
For example:
```ts
const myCustoms = await helloModuleService
.listMyCustoms()
const posts = await blogModuleService
.listPosts()
// with filters
const myCustoms = await helloModuleService
.listMyCustoms({
const posts = await blogModuleService
.listPosts({
id: ["123"]
})
```
</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.
@@ -108,122 +108,122 @@ Find a complete reference of each of the methods in [this documentation](!resour
```ts
const [
myCustoms,
posts,
count
] = await helloModuleService.listAndCountMyCustoms()
] = await blogModuleService.listAndCountPosts()
// with filters
const [
myCustoms,
posts,
count
] = await helloModuleService.listAndCountMyCustoms({
] = await blogModuleService.listAndCountPosts({
id: ["123"]
})
```
</TabsContent>
<TabsContent value="retrieveMyCustom">
<TabsContent value="retrievePost">
### retrieveMyCustom
### retrievePost
This method retrieves a record by its ID.
For example:
```ts
const myCustom = await helloModuleService
.retrieveMyCustom("123")
const post = await blogModuleService
.retrievePost("123")
```
</TabsContent>
<TabsContent value="createMyCustoms">
<TabsContent value="createPosts">
### createMyCustoms
### createPosts
This method creates and retrieves records of the data model.
For example:
```ts
const myCustom = await helloModuleService
.createMyCustoms({
name: "test"
const post = await blogModuleService
.createPosts({
title: "test"
})
// create multiple
const myCustoms = await helloModuleService
.createMyCustoms([
const posts = await blogModuleService
.createPosts([
{
name: "test"
title: "test"
},
{
name: "test 2"
title: "test 2"
},
])
```
</TabsContent>
<TabsContent value="updateMyCustoms">
<TabsContent value="updatePosts">
### updateMyCustoms
### updatePosts
This method updates and retrieves records of the data model.
For example:
```ts
const myCustom = await helloModuleService
.updateMyCustoms({
const post = await blogModuleService
.updatePosts({
id: "123",
name: "test"
title: "test"
})
// update multiple
const myCustoms = await helloModuleService
.updateMyCustoms([
const posts = await blogModuleService
.updatePosts([
{
id: "123",
name: "test"
title: "test"
},
{
id: "321",
name: "test 2"
title: "test 2"
},
])
// use filters
const myCustoms = await helloModuleService
.updateMyCustoms([
const posts = await blogModuleService
.updatePosts([
{
selector: {
id: ["123", "321"]
},
data: {
name: "test"
title: "test"
}
},
])
```
</TabsContent>
<TabsContent value="deleteMyCustoms">
<TabsContent value="deletePosts">
### deleteMyCustoms
### deletePosts
This method deletes records by an ID or filter.
For example:
```ts
await helloModuleService.deleteMyCustoms("123")
await blogModuleService.deletePosts("123")
// delete multiple
await helloModuleService.deleteMyCustoms([
await blogModuleService.deletePosts([
"123", "321"
])
// use filters
await helloModuleService.deleteMyCustoms({
await blogModuleService.deletePosts({
selector: {
id: ["123", "321"]
}
@@ -231,44 +231,44 @@ Find a complete reference of each of the methods in [this documentation](!resour
```
</TabsContent>
<TabsContent value="softDeleteMyCustoms">
<TabsContent value="softDeletePosts">
### softDeleteMyCustoms
### softDeletePosts
This method soft-deletes records using an array of IDs or an object of filters.
For example:
```ts
await helloModuleService.softDeleteMyCustoms("123")
await blogModuleService.softDeletePosts("123")
// soft-delete multiple
await helloModuleService.softDeleteMyCustoms([
await blogModuleService.softDeletePosts([
"123", "321"
])
// use filters
await helloModuleService.softDeleteMyCustoms({
await blogModuleService.softDeletePosts({
id: ["123", "321"]
})
```
</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.
For example:
```ts
await helloModuleService.restoreMyCustoms([
await blogModuleService.restorePosts([
"123", "321"
])
// use filters
await helloModuleService.restoreMyCustoms({
await blogModuleService.restorePosts({
id: ["123", "321"]
})
```
@@ -285,15 +285,15 @@ For example:
```ts highlights={[["8"]]}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
import Post from "./models/post"
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
Post,
}){
constructor() {
super(...arguments)
}
}
export default HelloModuleService
export default BlogModuleService
```

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/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/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/modules/loaders/page.mdx": "2024-12-09T10:32:29.221Z",
"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/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/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/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/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/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/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/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/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",
@@ -52,25 +52,25 @@ export const generatedEditDates = {
"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/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/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/unit-tests/module-example/page.mdx": "2024-09-02T11:04:27.232Z",
"app/learn/debugging-and-testing/testing-tools/unit-tests/page.mdx": "2024-09-02T11:03:26.997Z",
"app/learn/fundamentals/modules/service-constraints/page.mdx": "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/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/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/page.mdx": "2025-01-31T13:19:02.587Z",
"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-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/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/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/multiple-services/page.mdx": "2024-10-21T13:30:21.370Z",
"app/learn/fundamentals/modules/db-operations/page.mdx": "2025-03-18T15:10:57.351Z",
"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/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",

File diff suppressed because it is too large Load Diff

View File

@@ -713,44 +713,44 @@ Find this example explained in details in [this documentation](!docs!/learn/fund
</Note>
1. Create the directory `src/modules/hello`.
2. Create the file `src/modules/hello/models/my-custom.ts` with the following data model:
1. Create the directory `src/modules/blog`.
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"
const MyCustom = model.define("my_custom", {
const Post = model.define("post", {
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 MyCustom from "./models/my-custom"
import Post from "./models/post"
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
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"
import HelloModuleService from "./service"
```ts title="src/modules/blog/index.ts"
import BlogModuleService from "./service"
import { Module } from "@medusajs/framework/utils"
export const HELLO_MODULE = "helloModuleService"
export const BLOG_MODULE = "blog"
export default Module(HELLO_MODULE, {
service: HelloModuleService,
export default Module(BLOG_MODULE, {
service: BlogModuleService,
})
```
@@ -763,7 +763,7 @@ module.exports = defineConfig({
},
modules: [
{
resolve: "./modules/hello",
resolve: "./modules/blog",
},
],
})
@@ -772,7 +772,7 @@ module.exports = defineConfig({
6. Generate and run migrations:
```bash
npx medusa db:generate helloModuleService
npx medusa db:generate blog
npx medusa db:migrate
```
@@ -780,23 +780,23 @@ npx medusa db:migrate
```ts title="src/api/custom/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import HelloModuleService from "../../modules/hello/service"
import { HELLO_MODULE } from "../../modules/hello"
import BlogModuleService from "../../modules/blog/service"
import { BLOG_MODULE } from "../../modules/blog"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const helloModuleService: HelloModuleService = req.scope.resolve(
HELLO_MODULE
const blogModuleService: BlogModuleService = req.scope.resolve(
BLOG_MODULE
)
const my_custom = await helloModuleService.createMyCustoms({
name: "test",
const post = await blogModuleService.createPosts({
title: "test",
})
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.
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"
export class CustomService {
```ts title="src/modules/blog/services/category.ts"
export class CategoryService {
// 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"
export * from "./custom"
```ts title="src/modules/blog/services/index.ts"
export * from "./category"
```
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 MyCustom from "./models/my-custom"
import { CustomService } from "./services"
import Post from "./models/post"
import { CategoryService } from "./services"
type InjectedDependencies = {
customService: CustomService
categoryService: CategoryService
}
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
Post,
}){
private customService: CustomService
private categoryService: CategoryService
constructor({ customService }: InjectedDependencies) {
constructor({ categoryService }: InjectedDependencies) {
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).
@@ -860,7 +860,7 @@ module.exports = defineConfig({
// ...
modules: [
{
resolve: "./modules/hello",
resolve: "./modules/blog",
options: {
apiKey: true,
},
@@ -871,17 +871,17 @@ module.exports = defineConfig({
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 MyCustom from "./models/my-custom"
import Post from "./models/post"
// recommended to define type in another file
type ModuleOptions = {
apiKey?: boolean
}
export default class HelloModuleService extends MedusaService({
MyCustom,
export default class BlogModuleService extends MedusaService({
Post,
}){
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:
```ts title="src/modules/hello/service.ts"
```ts title="src/modules/blog/service.ts"
import { Logger } from "@medusajs/framework/types"
import { BRAND_MODULE } from ".."
import { BLOG_MODULE } from ".."
export type ModuleOptions = {
apiKey: string
@@ -915,7 +915,7 @@ type InjectedDependencies = {
logger: Logger
}
export class BrandClient {
export class BlogClient {
private options_: ModuleOptions
private logger_: Logger
@@ -956,23 +956,23 @@ This assumes you already have a module. If not, follow [this example](#create-mo
</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"
const MyCustom = model.define("my_custom", {
const Post = model.define("post", {
id: model.id().primaryKey(),
name: model.text(),
title: model.text(),
})
export default MyCustom
export default Post
```
2. Generate and run migrations:
```bash
npx medusa db:generate helloModuleService
npx medusa db:generate blog
npx medusa db:migrate
```
@@ -985,7 +985,7 @@ A data model can have properties of the following types:
1. ID property:
```ts
const MyCustom = model.define("my_custom", {
const Post = model.define("post", {
id: model.id(),
// ...
})
@@ -994,8 +994,8 @@ const MyCustom = model.define("my_custom", {
2. Text property:
```ts
const MyCustom = model.define("my_custom", {
name: model.text(),
const Post = model.define("post", {
title: model.text(),
// ...
})
```
@@ -1003,8 +1003,8 @@ const MyCustom = model.define("my_custom", {
3. Number property:
```ts
const MyCustom = model.define("my_custom", {
age: model.number(),
const Post = model.define("post", {
views: model.number(),
// ...
})
```
@@ -1012,7 +1012,7 @@ const MyCustom = model.define("my_custom", {
4. Big Number property:
```ts
const MyCustom = model.define("my_custom", {
const Post = model.define("post", {
price: model.bigNumber(),
// ...
})
@@ -1021,8 +1021,8 @@ const MyCustom = model.define("my_custom", {
5. Boolean property:
```ts
const MyCustom = model.define("my_custom", {
hasAccount: model.boolean(),
const Post = model.define("post", {
isPublished: model.boolean(),
// ...
})
```
@@ -1030,8 +1030,8 @@ const MyCustom = model.define("my_custom", {
6. Enum property:
```ts
const MyCustom = model.define("my_custom", {
color: model.enum(["black", "white"]),
const Post = model.define("post", {
status: model.enum(["draft", "published"]),
// ...
})
```
@@ -1039,8 +1039,8 @@ const MyCustom = model.define("my_custom", {
7. Date-Time property:
```ts
const MyCustom = model.define("my_custom", {
date_of_birth: model.dateTime(),
const Post = model.define("post", {
publishedAt: model.dateTime(),
// ...
})
```
@@ -1048,7 +1048,7 @@ const MyCustom = model.define("my_custom", {
8. JSON property:
```ts
const MyCustom = model.define("my_custom", {
const Post = model.define("post", {
metadata: model.json(),
// ...
})
@@ -1057,8 +1057,8 @@ const MyCustom = model.define("my_custom", {
9. Array property:
```ts
const MyCustom = model.define("my_custom", {
names: model.array(),
const Post = model.define("post", {
tags: model.array(),
// ...
})
```
@@ -1072,12 +1072,12 @@ To set an `id` property as the primary key of a data model:
```ts highlights={[["4", "primaryKey"]]}
import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", {
const Post = model.define("post", {
id: model.id().primaryKey(),
// ...
})
export default MyCustom
export default Post
```
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"]]}
import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", {
name: model.text().primaryKey(),
const Post = model.define("post", {
title: model.text().primaryKey(),
// ...
})
export default MyCustom
export default Post
```
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"]]}
import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", {
age: model.number().primaryKey(),
const Post = model.define("post", {
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).
@@ -1115,17 +1115,17 @@ To set the default value of a property:
```ts highlights={[["6"], ["9"]]}
import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", {
color: model
.enum(["black", "white"])
.default("black"),
age: model
const Post = model.define("post", {
status: model
.enum(["draft", "published"])
.default("draft"),
views: model
.number()
.default(0),
// ...
})
export default MyCustom
export default Post
```
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"]]}
import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", {
const Post = model.define("post", {
price: model.bigNumber().nullable(),
// ...
})
export default MyCustom
export default Post
```
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"]]}
import { model } from "@medusajs/framework/utils"
const User = model.define("user", {
email: model.text().unique(),
const Post = model.define("post", {
title: model.text().unique(),
// ...
})
export default User
export default Post
```
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", {
id: model.id().primaryKey(),
name: model.text().index(
"IDX_MY_CUSTOM_NAME"
title: model.text().index(
"IDX_POST_TITLE"
),
})
@@ -1217,24 +1217,24 @@ To make a property searchable using terms or keywords:
```ts highlights={[["4", "searchable"]]}
import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", {
name: model.text().searchable(),
const Post = model.define("post", {
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:
<Note>
`helloModuleService` is the main service that the data models belong to.
`blogModuleService` is the main service that manages the `Post` data model.
</Note>
```ts
const myCustoms = await helloModuleService.listMyCustoms({
const posts = await blogModuleService.listPosts({
q: "John",
})
```
@@ -1339,19 +1339,19 @@ To set the ID of the user that an email belongs to:
<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>
```ts
// when creating an email
const email = await helloModuleService.createEmails({
const email = await blogModuleService.createEmails({
// other properties...
user: "123",
})
// when updating an email
const email = await helloModuleService.updateEmails({
const email = await blogModuleService.updateEmails({
id: "321",
// other properties...
user: "123",
@@ -1362,13 +1362,13 @@ And to set the ID of a user's email when creating or updating it:
```ts
// when creating a user
const user = await helloModuleService.createUsers({
const user = await blogModuleService.createUsers({
// other properties...
email: "123",
})
// when updating a user
const user = await helloModuleService.updateUsers({
const user = await blogModuleService.updateUsers({
id: "321",
// other properties...
email: "123",
@@ -1385,19 +1385,19 @@ To set the ID of the store that a product belongs to:
<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>
```ts
// when creating a product
const product = await helloModuleService.createProducts({
const product = await blogModuleService.createProducts({
// other properties...
store_id: "123",
})
// when updating a product
const product = await helloModuleService.updateProducts({
const product = await blogModuleService.updateProducts({
id: "321",
// other properties...
store_id: "123",
@@ -1414,12 +1414,12 @@ To set the orders a product has when creating it:
<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>
```ts
const product = await helloModuleService.createProducts({
const product = await blogModuleService.createProducts({
// other properties...
orders: ["123", "321"],
})
@@ -1428,14 +1428,14 @@ const product = await helloModuleService.createProducts({
To add new orders to a product without removing the previous associations:
```ts
const product = await helloModuleService.retrieveProduct(
const product = await blogModuleService.retrieveProduct(
"123",
{
relations: ["orders"],
}
)
const updatedProduct = await helloModuleService.updateProducts({
const updatedProduct = await blogModuleService.updateProducts({
id: product.id,
// other properties...
orders: [
@@ -1453,12 +1453,12 @@ To retrieve records related to a data model's records through a relation, pass t
<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>
```ts highlights={[["4", "relations"]]}
const product = await helloModuleService.retrieveProducts(
const product = await blogModuleService.retrieveProducts(
"123",
{
relations: ["orders"],
@@ -1482,18 +1482,18 @@ To extend the service factory in your module's service:
```ts highlights={[["4", "MedusaService"]]}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
import Post from "./models/post"
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
Post,
}){
// 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.
@@ -1509,14 +1509,14 @@ To resolve resources from the module's container in a service:
```ts highlights={[["14"]]}
import { Logger } from "@medusajs/framework/types"
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
import Post from "./models/post"
type InjectedDependencies = {
logger: Logger
}
class HelloModuleService extends MedusaService({
MyCustom,
class BlogModuleService extends MedusaService({
Post,
}){
protected logger_: Logger
@@ -1524,13 +1524,13 @@ class HelloModuleService extends MedusaService({
super(...arguments)
this.logger_ = logger
this.logger_.info("[HelloModuleService]: Hello World!")
this.logger_.info("[BlogModuleService]: Hello World!")
}
// ...
}
export default HelloModuleService
export default BlogModuleService
```
</CodeTab>
@@ -1543,13 +1543,13 @@ type InjectedDependencies = {
logger: Logger
}
export default class HelloModuleService {
export default class BlogModuleService {
protected logger_: Logger
constructor({ logger }: InjectedDependencies) {
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"]]}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
import Post from "./models/post"
// recommended to define type in another file
type ModuleOptions = {
apiKey?: boolean
}
export default class HelloModuleService extends MedusaService({
MyCustom,
export default class BlogModuleService extends MedusaService({
Post,
}){
protected options_: ModuleOptions
@@ -1604,14 +1604,14 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
class HelloModuleService {
class BlogModuleService {
// ...
@InjectManager()
async getCount(
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<number> {
return await sharedContext.manager.count("my_custom")
return await sharedContext.manager.count("post")
}
@InjectManager()
@@ -1619,7 +1619,7 @@ class HelloModuleService {
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<number> {
const data = await sharedContext.manager.execute(
"SELECT COUNT(*) as num FROM my_custom"
"SELECT COUNT(*) as num FROM post"
)
return parseInt(data[0].num)
@@ -1642,7 +1642,7 @@ import {
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
class BlogModuleService {
// ...
@InjectTransactionManager()
protected async update_(
@@ -1654,7 +1654,7 @@ class HelloModuleService {
): Promise<any> {
const transactionManager = sharedContext.transactionManager
await transactionManager.nativeUpdate(
"my_custom",
"post",
{
id: input.id,
},
@@ -1665,7 +1665,7 @@ class HelloModuleService {
// retrieve again
const updatedRecord = await transactionManager.execute(
`SELECT * FROM my_custom WHERE id = '${input.id}'`
`SELECT * FROM post WHERE id = '${input.id}'`
)
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:
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"
import HelloModule from "../modules/hello"
```ts title="src/links/blog-product.ts"
import BlogModule from "../modules/blog"
import ProductModule from "@medusajs/medusa/product"
import { defineLink } from "@medusajs/framework/utils"
export default defineLink(
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:
```ts highlights={[["9", "isList"]]}
import HelloModule from "../modules/hello"
import BlogModule from "../modules/blog"
import ProductModule from "@medusajs/medusa/product"
import { defineLink } from "@medusajs/framework/utils"
export default defineLink(
ProductModule.linkable.product,
{
linkable: HelloModule.linkable.myCustom,
linkable: BlogModule.linkable.post,
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:
```ts highlights={[["9", "deleteCascades"]]}
import HelloModule from "../modules/hello"
import BlogModule from "../modules/blog"
import ProductModule from "@medusajs/medusa/product"
import { defineLink } from "@medusajs/framework/utils"
export default defineLink(
ProductModule.linkable.product,
{
linkable: HelloModule.linkable.myCustom,
linkable: BlogModule.linkable.post,
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:
```ts highlights={[["9", "database"]]}
import HelloModule from "../modules/hello"
import BlogModule from "../modules/blog"
import ProductModule from "@medusajs/medusa/product"
import { defineLink } from "@medusajs/framework/utils"
export default defineLink(
ProductModule.linkable.product,
HelloModule.linkable.myCustom,
BlogModule.linkable.post,
{
database: {
extraColumns: {
@@ -1802,13 +1802,13 @@ await link.create({
To retrieve the custom column when retrieving linked records using Query:
```ts
import productHelloLink from "../links/product-hello"
import productBlogLink from "../links/product-blog"
// ...
const { data } = await query.graph({
entity: productHelloLink.entryPoint,
fields: ["metadata", "product.*", "my_custom.*"],
entity: productBlogLink.entryPoint,
fields: ["metadata", "product.*", "post.*"],
filters: {
product_id: "prod_123",
},
@@ -1823,7 +1823,7 @@ To create a link between two records using Link:
```ts
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
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]: {
product_id: "prod_123",
},
[HELLO_MODULE]: {
my_custom_id: "mc_123",
[BLOG_MODULE]: {
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.
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 { HELLO_MODULE } from ".."
import HelloModuleService from "../service"
import MyCustom from "../models/my-custom"
import { BLOG_MODULE } from ".."
import BlogModuleService from "../service"
import Post from "../models/post"
moduleIntegrationTestRunner<HelloModuleService>({
moduleName: HELLO_MODULE,
moduleModels: [MyCustom],
resolve: "./modules/hello",
moduleIntegrationTestRunner<BlogModuleService>({
moduleName: BLOG_MODULE,
moduleModels: [Post],
resolve: "./modules/blog",
testSuite: ({ service }) => {
describe("HelloModuleService", () => {
describe("BlogModuleService", () => {
it("says hello world", () => {
const message = service.getMessage()

View File

@@ -411,13 +411,15 @@ The workflows steps are:
type: "step",
name: "createVendorStep",
description: "Create the vendor.",
depth: 1
depth: 1,
link: "#createvendorstep"
},
{
type: "step",
name: "createVendorAdminStep",
description: "Create the vendor admin.",
depth: 1
depth: 1,
link: "#createvendoradminstep"
},
{
type: "step",
@@ -910,7 +912,7 @@ The workflow to create a product has the following steps:
<WorkflowDiagram
workflow={{
nam: "createVendorProductWorkflow",
name: "createVendorProductWorkflow",
steps: [
{
type: "step",
@@ -1280,7 +1282,8 @@ In this step, youll create a workflow thats executed when the customer pla
type: "step",
name: "groupVendorItemsStep",
description: "Group the items by their vendor.",
depth: 1
depth: 1,
link: "#groupvendoritemssstep"
},
{
type: "step",
@@ -1293,7 +1296,8 @@ In this step, youll create a workflow thats executed when the customer pla
type: "step",
name: "createVendorOrdersStep",
description: "Create child orders for each vendor.",
depth: 1
depth: 1,
link: "#createvendorordersstep"
},
{
type: "step",

View File

@@ -12,14 +12,14 @@ This document provides a reference to the `moduleIntegrationTestRunner` function
```ts
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import { HELLO_MODULE } from ".."
import HelloModuleService from "../service"
import MyCustom from "../models/my-custom"
import { BLOG_MODULE } from ".."
import BlogModuleService from "../service"
import Post from "../models/post"
moduleIntegrationTestRunner<HelloModuleService>({
moduleName: HELLO_MODULE,
moduleModels: [MyCustom],
resolve: "./src/modules/hello",
moduleIntegrationTestRunner<BlogModuleService>({
moduleName: BLOG_MODULE,
moduleModels: [Post],
resolve: "./src/modules/blog",
testSuite: ({ service }) => {
// TODO write tests
},

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/page.mdx": "2025-02-26T12:37:12.721Z",
"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/multi-region-store/page.mdx": "2025-02-26T12:38:50.292Z",
"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/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/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",
"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",
@@ -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/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/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/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",

View File

@@ -1,14 +1,50 @@
import React from "react"
import { InlineCode } from "../../../InlineCode"
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 (
<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 size-[20px] items-center justify-center text-medusa-tag-orange-icon">
<Bolt />
<div className="flex size-[20px] items-center justify-center">
<CursorArrowRays />
</div>
<Text
size="xsmall"
@@ -16,20 +52,7 @@ export const WorkflowDiagramLegend = () => {
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>
View step details
</Text>
</div>
</div>

View File

@@ -21,7 +21,7 @@ export const WorkflowDiagramList = ({
<WorkflowDiagramListDepth cluster={cluster} next={next} key={depth} />
)
})}
{!hideLegend && <WorkflowDiagramLegend />}
<WorkflowDiagramLegend hideLegend={hideLegend} />
</div>
)
}