docs: added to workflow legend + example improvements (#11895)
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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],
|
||||
// ...
|
||||
})
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -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!")
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
@@ -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_(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,7 +23,7 @@ module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "./src/modules/hello",
|
||||
resolve: "./src/modules/blog",
|
||||
options: {
|
||||
capitalize: true,
|
||||
},
|
||||
@@ -64,17 +64,17 @@ The module’s 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 module’s 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],
|
||||
})
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -411,13 +411,15 @@ The workflow’s 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, you’ll create a workflow that’s 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, you’ll create a workflow that’s executed when the customer pla
|
||||
type: "step",
|
||||
name: "createVendorOrdersStep",
|
||||
description: "Create child orders for each vendor.",
|
||||
depth: 1
|
||||
depth: 1,
|
||||
link: "#createvendorordersstep"
|
||||
},
|
||||
{
|
||||
type: "step",
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -21,7 +21,7 @@ export const WorkflowDiagramList = ({
|
||||
<WorkflowDiagramListDepth cluster={cluster} next={next} key={depth} />
|
||||
)
|
||||
})}
|
||||
{!hideLegend && <WorkflowDiagramLegend />}
|
||||
<WorkflowDiagramLegend hideLegend={hideLegend} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user