diff --git a/www/apps/book/app/_more-resources/page.mdx b/www/apps/book/app/_more-resources/page.mdx
deleted file mode 100644
index 61b99cdbb1..0000000000
--- a/www/apps/book/app/_more-resources/page.mdx
+++ /dev/null
@@ -1,37 +0,0 @@
-export const metadata = {
- title: `${pageNumber} More Resources`,
-}
-
-# {metadata.title}
-
-In this chapter, you’ll find more resources to aid you during your development with Medusa.
-
-## Medusa Resources Documentation
-
-The Medusa Resources documentation provides guides and references that are useful for your development. This book included links to parts of the Medusa Resources documentation where necessary.
-
-The following sections highlight other parts of the Medusa Resources documentation that weren’t mentioned before. These are resources that you’re likely to go back to during your development.
-
----
-
-## Troubleshooting Guides
-
-During your development, you may run into common issues.
-
-Refer to [this Medusa Resources documentation](!resources!/troubleshooting) for a full list of troubleshooting guides to find a fix.
-
----
-
-## Recipes
-
-Recipes provide a general overview of how to implement a common use case with Medusa, such as building a marketplace or supporting digital products.
-
-Refer to [this Medusa Resources documentation](!resources!#recipes) for a full list of available recipes.
-
----
-
-## SDK References
-
-Medusa provides SDKs and CLI tools to aid you in your development.
-
-Refer to [this Medusa Resources documentation](!resources!#sdks-and-tools) for a full list of available tools and how to use them.
diff --git a/www/apps/book/app/advanced-development/services/data-management-tips/page.mdx b/www/apps/book/app/advanced-development/_services/data-management-tips/page.mdx
similarity index 100%
rename from www/apps/book/app/advanced-development/services/data-management-tips/page.mdx
rename to www/apps/book/app/advanced-development/_services/data-management-tips/page.mdx
diff --git a/www/apps/book/app/advanced-development/services/data-management/page.mdx b/www/apps/book/app/advanced-development/_services/data-management/page.mdx
similarity index 100%
rename from www/apps/book/app/advanced-development/services/data-management/page.mdx
rename to www/apps/book/app/advanced-development/_services/data-management/page.mdx
diff --git a/www/apps/book/app/advanced-development/services/transactions/page.mdx b/www/apps/book/app/advanced-development/_services/transactions/page.mdx
similarity index 100%
rename from www/apps/book/app/advanced-development/services/transactions/page.mdx
rename to www/apps/book/app/advanced-development/_services/transactions/page.mdx
diff --git a/www/apps/book/app/advanced-development/admin/widgets/page.mdx b/www/apps/book/app/advanced-development/admin/widgets/page.mdx
index 343ffd564e..b00d261031 100644
--- a/www/apps/book/app/advanced-development/admin/widgets/page.mdx
+++ b/www/apps/book/app/advanced-development/admin/widgets/page.mdx
@@ -24,7 +24,7 @@ For example, you can add a widget on the order details page that shows payment d
## How to Create a Widget?
-A widget is created in a file under the `src/admin/widgets` directory. The file’s default export must be the widget, which is the React component, and must also export the widget’s configurations.
+A widget is created in a file under the `src/admin/widgets` directory. The file’s default export must be the widget, which is the React component. The file must also export the widget’s configurations.
For example, create the file `src/admin/widgets/product-widget.tsx` with the following content:
diff --git a/www/apps/book/app/advanced-development/api-routes/cors/page.mdx b/www/apps/book/app/advanced-development/api-routes/cors/page.mdx
index 9b8d177071..2ca6ba8fd4 100644
--- a/www/apps/book/app/advanced-development/api-routes/cors/page.mdx
+++ b/www/apps/book/app/advanced-development/api-routes/cors/page.mdx
@@ -12,7 +12,7 @@ Cross-Origin Resource Sharing (CORS) allows only configured origins to access yo
### CORS Configurations
-You can configure allowed origins for Store and Admin API Routes using the `store_cors` and `admin_cors` configurations in `medusa-config.js`. Each of these configurations accepts a URL pattern to identify allowed origins.
+You configure allowed origins for Store and Admin API Routes using the `store_cors` and `admin_cors` configurations in `medusa-config.js`. Each of these configurations accepts a URL pattern to identify allowed origins.
For example:
@@ -63,7 +63,7 @@ This disables the CORS middleware on API Routes at the path `/store/custom`.
## CORS in Custom Routes
-If you create a route that doesn’t start with `/store` or `/admin`, you must apply the CORS middleware manually. Otherwise, all requests to your API route cause a CORS error.
+If you create a route that doesn’t start with `/store` or `/admin`, you must apply the CORS middleware manually. Otherwise, all requests to your API route lead to a CORS error.
You can do that in the exported middlewares configurations in `src/api/middlewares.ts`.
diff --git a/www/apps/book/app/advanced-development/api-routes/http-methods/page.mdx b/www/apps/book/app/advanced-development/api-routes/http-methods/page.mdx
index 599b04a9db..71ef9f0700 100644
--- a/www/apps/book/app/advanced-development/api-routes/http-methods/page.mdx
+++ b/www/apps/book/app/advanced-development/api-routes/http-methods/page.mdx
@@ -8,12 +8,10 @@ In this chapter, you'll learn about how to add new API routes for each HTTP meth
## Handlers of HTTP Methods
-You can define a handler function for each HTTP method in a route file. The function’s name is the name of the HTTP method it handles.
+You can export handler functions for more than one HTTP method in a route file. An API route is created for every HTTP method you export a function for.
Allowed HTTP methods are: `GET`, `POST`, `DELETE`, `PUT`, `PATCH`, `OPTIONS`, and `HEAD`.
-Creating a route handler function for any of the above HTTP methods exposes a new API route for that method.
-
For example, create the file `src/api/store/hello-world/route.ts` with the following content:
```ts title="src/api/store/hello-world/route.ts"
diff --git a/www/apps/book/app/advanced-development/api-routes/middlewares/page.mdx b/www/apps/book/app/advanced-development/api-routes/middlewares/page.mdx
index 70e4e8e78a..1c57d4d11e 100644
--- a/www/apps/book/app/advanced-development/api-routes/middlewares/page.mdx
+++ b/www/apps/book/app/advanced-development/api-routes/middlewares/page.mdx
@@ -14,11 +14,11 @@ A middleware is a function executed when a request is sent to an API Route.
## How to Create a Middleware?
-Middlewares are defined in the special file `src/api/middleware.ts`. The file must export an object of middleware configurations.
+Middlewares are defined in the special file `src/api/middlewares.ts`. The file must export an object of middleware configurations.
For example:
-```ts title="src/api/middleware.ts"
+```ts title="src/api/middlewares.ts"
import { MiddlewaresConfig } from "@medusajs/medusa"
export const config: MiddlewaresConfig = {
@@ -49,19 +49,16 @@ The `matcher` property can be a string or a regular expression.
### Test Middleware
-To test the middleware, start the application:
+To test the middleware:
+
+1. Start the application:
```bash npm2yarn
npm run dev
```
-Then, send a request to any API route starting with `/store`, such as `localhost:9000/store/products`:
-
-```bash apiTesting testApiUrl="localhost:9000/store/products" testApiMethod="GET"
-curl localhost:9000/store/products
-```
-
-Once you send the request, you’ll see the following message in the terminal:
+2. Send a request to any API route starting with `/store`.
+3. See the following message in the terminal:
```bash
Received a request!
@@ -69,13 +66,12 @@ Received a request!
---
-## When to Use
+## When to Use Middlewares
-- You want to guard API routes by a custom condition.
+- You want to protect API routes by a custom condition.
- You're modifying the request body.
-- You're registering custom resources in the Medusa container.
@@ -101,8 +97,6 @@ You must call the `next` function in the middleware. Otherwise, other middleware
In addition to the `matcher` configuration, you can restrict which HTTP methods the middleware is applied to.
-The object in the `routes` array accepts the property `method` whose value is either a string or an array of strings. Each string is an allowed HTTP method. If no value is specified, the middlewares are applied to requests of all HTTP methods.
-
For example:
export const highlights = [["7", "", "Apply the middleware only on `POST` requests"]]
@@ -114,7 +108,7 @@ export const config: MiddlewaresConfig = {
routes: [
{
matcher: "/store*",
- method: "POST",
+ method: ["POST", "PUT"],
middlewares: [
(req, res, next) => {
console.log("Received a request!")
@@ -127,4 +121,6 @@ export const config: MiddlewaresConfig = {
}
```
-This applies the middleware only when a `POST` request is sent to an API route path starting with `/store`.
+The object in the `routes` array accepts the property `method` whose value is one or more HTTP methods to apply the middleware to.
+
+This applies the middleware only when a `POST` or `PUT` request is sent to an API route path starting with `/store`.
diff --git a/www/apps/book/app/advanced-development/api-routes/parameters/page.mdx b/www/apps/book/app/advanced-development/api-routes/parameters/page.mdx
index 5e3aa41446..90a90e486f 100644
--- a/www/apps/book/app/advanced-development/api-routes/parameters/page.mdx
+++ b/www/apps/book/app/advanced-development/api-routes/parameters/page.mdx
@@ -8,9 +8,9 @@ In this chapter, you’ll learn about path, query, and request body parameters.
## Path Parameters
-To define a path parameter for an API route, create a directory as part of the route file’s path. The directory’s name is of the format `[param]`, where `param` is the name of the parameters.
+To create an API route that accepts a path parameter, create a directory within the route's path whose name is of the format `[param]`.
-For example, to create an API Route at the path `/message/{id}`, where `{id}` is an ID that can be passed to the route, create the file `src/api/store/hello-world/[id]/route.ts` with the following content:
+For example, to create an API Route at the path `/message/{id}`, where `{id}` is a path parameter, create the file `src/api/store/hello-world/[id]/route.ts` with the following content:
export const singlePathHighlights = [
["11", "req.params.id", "Access the path parameter `id`"]
@@ -32,13 +32,13 @@ export const GET = (
}
```
-You can access the path parameter using the `params` property of the `MedusaRequest` parameter. The `params` property is an object whose keys are the parameter names, and the values are each parameter’s value passed in the route’s path.
+The `MedusaRequest` object has a `params` property. `params` holds the path parameters in key-value pairs.
### Multiple Path Parameters
-Each directory in the route file’s path whose name is of the format `[param]` is considered a path parameter. However, every parameter name must be unique.
+To create an API route that accepts multiple path parameters, create within the file's path multiple directories whose names are of the format `[param]`.
-For example, you can create an API route at `src/api/store/hello-world/[id]/name/[name]/route.ts`:
+For example, create an API route at `src/api/store/hello-world/[id]/name/[name]/route.ts`:
export const multiplePathHighlights = [
["11", "req.params.id", "Access the path parameter `id`"],
@@ -67,7 +67,7 @@ This API route expects two path parameters: `id` and `name`.
## Query Parameters
-You can access all query parameters in the `query` property of the `MedusaRequest` parameter.
+You can access all query parameters in the `query` property of the `MedusaRequest` object.
For example:
@@ -95,11 +95,12 @@ export async function GET(
## Request Body Parameters
-By default, any request sent to your Medusa application with its `Content-Type` header set to `application/json` is parsed into an object and attached to the `MedusaRequest`'s `body` property.
+The Medusa application parses the body of any request having its `Content-Type` header set to `application/json`. The request body parameters are set in the `MedusaRequest`'s `body` property.
For example:
export const bodyHighlights = [
+ ["11", "HelloWorldReq", "Specify the type of the request body parameters."],
["15", "req.body.name", "Access the request body parameter `name`"],
]
@@ -123,18 +124,16 @@ export const POST = (
}
```
-The `MedusaRequest` type accepts a type argument indicating the expected request body parameters.
-
In this example, you use the `name` request body parameter to create the message in the returned response.
To test it out, send the following request to your Medusa application:
```bash apiTesting testApiUrl="http://localhost:9000/store/hello-world" testApiMethod="POST" testBodyParams={{ "name": "John" }}
-curl -X POST http://localhost:9000/store/hello-world \
- --header 'Content-Type: application/json' \
- --data-raw '{
- "name": "John"
- }'
+curl -X POST 'http://localhost:9000/store/hello-world' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "name": "John"
+}'
```
This returns the following JSON object:
diff --git a/www/apps/book/app/advanced-development/api-routes/protected-routes/page.mdx b/www/apps/book/app/advanced-development/api-routes/protected-routes/page.mdx
index 8d3af2030c..df7eb8ed80 100644
--- a/www/apps/book/app/advanced-development/api-routes/protected-routes/page.mdx
+++ b/www/apps/book/app/advanced-development/api-routes/protected-routes/page.mdx
@@ -57,9 +57,7 @@ You can access the logged-in customer’s ID in all API routes starting with `/s
For example:
-
-
-```ts title="src/api/store/custom/route.ts" highlights={[["16", "", "Access the logged-in customer's ID."]]}
+```ts title="src/api/store/me/custom/route.ts" highlights={[["16", "", "Access the logged-in customer's ID."]]}
import type {
MedusaRequest,
MedusaResponse,
@@ -122,9 +120,9 @@ In the route handler, you resolve the `UserService`, and then use it to retrieve
To protect custom API Routes that don’t start with `/store/me` or `/admin`, apply one of the following middlewares exported by the `@medusajs/medusa` package on your routes:
-- `authenticate`: only authenticated admin users can access the API Route. You can access the user's ID in the API Route method handler using the `MedusaRequest` object's `user.userId`.
+- `authenticate`: only authenticated admin users can access the API Route.
- `authenticateCustomer`: customer authentication isn’t required, but if a customer is logged in, it attaches their ID to the `MedusaRequest` object's `user.customer_id`.
-- `requireCustomerAuthentication`: only authenticated customers can access the API Route. You can access the customer's ID in the API Route method handler using the `MedusaRequest` object's `user.customer_id`.
+- `requireCustomerAuthentication`: only authenticated customers can access the API Route.
For example:
diff --git a/www/apps/book/app/advanced-development/api-routes/request-body-parsers/page.mdx b/www/apps/book/app/advanced-development/api-routes/request-body-parsers/page.mdx
deleted file mode 100644
index 4d08997d64..0000000000
--- a/www/apps/book/app/advanced-development/api-routes/request-body-parsers/page.mdx
+++ /dev/null
@@ -1,66 +0,0 @@
-export const metadata = {
- title: `${pageNumber} Request Body Parsers`,
-}
-
-# {metadata.title}
-
-In this chapter, you’ll learn how to configure request-body parsing and add new parsers.
-
-## Add a Request-Body Parser
-
-You can parse request bodies of other content types by adding the parser as a middleware to the routes.
-
-For example:
-
-export const parserHighlights = [
- ["13", "", "Add a parser for the `application/x-www-form-urlencoded` content type."]
-]
-
-```ts title="src/api/middlewares.ts" highlights={parserHighlights}
-import type {
- MiddlewaresConfig,
-} from "@medusajs/medusa"
-import {
- urlencoded,
-} from "body-parser"
-
-export const config: MiddlewaresConfig = {
- routes: [
- {
- matcher: "*",
- middlewares: [
- urlencoded({ extended: true }),
- ],
- },
- ],
-}
-```
-
-This adds a parser for the `application/x-www-form-urlencoded` content type and attaches the parsed data to the `MedusaRequest` object’s `body` property.
-
----
-
-## Parse Webhook Body Parameters
-
-Webhook API Routes may require the `raw` body parser middleware rather than the default `json`.
-
-To change the default parser, set the `bodyParser` property of a middleware route object to `false`, and pass the preferred body-parser middleware in the `middlewares` property.
-
-For example:
-
-```ts title="src/api/middlewares.ts" highlights={[["8", "", "Disables the default request-body parser."], ["9", "raw", "Add a new body parser."]]}
-import { MiddlewaresConfig } from "@medusajs/medusa"
-import { raw } from "body-parser"
-
-export const config: MiddlewaresConfig = {
- routes: [
- {
- matcher: "/webhooks*",
- bodyParser: false,
- middlewares: [raw({ type: "application/json" })],
- },
- ],
-}
-```
-
-This changes the request-body parser to use the `raw` middleware on routes starting with `/webhooks`.
diff --git a/www/apps/book/app/advanced-development/data-models/common-definitions/page.mdx b/www/apps/book/app/advanced-development/data-models/common-definitions/page.mdx
index cb59e9f05c..cf440d39cf 100644
--- a/www/apps/book/app/advanced-development/data-models/common-definitions/page.mdx
+++ b/www/apps/book/app/advanced-development/data-models/common-definitions/page.mdx
@@ -19,13 +19,13 @@ Refer to MikroORM's documentation for more details related to your use case.
## Relations Between Data Models
-You can build relations between data models in the same module.
+You can build relations between data models in the same module using foreign keys.
-Refer to [MikroORM's relations definition](https://mikro-orm.io/docs/relationships)
+Refer to [MikroORM's relations definitions](https://mikro-orm.io/docs/relationships) for more details.
-For building relationships between data models in different module, refer to the [Module Relationships chapter](../../modules/module-relationships/page.mdx) instead.
+For building relationships between data models in different modules, refer to the [Module Relationships chapter](../../modules/module-relationships/page.mdx) instead.
@@ -61,7 +61,7 @@ const VariantIdIndex = createPsqlIndexStatementHelper({
columns: "variant_id",
}).MikroORMIndex
-@Entity({ tableName: "product_media" })
+@Entity()
export default class ProductMedia {
@PrimaryKey({ columnType: "text" })
id: string
@@ -92,6 +92,11 @@ export default class ProductMedia {
onCreate() {
this.id = generateEntityId(this.id, "promed")
}
+
+ @OnInit()
+ OnInit() {
+ this.id = generateEntityId(this.id, "promed")
+ }
}
```
diff --git a/www/apps/book/app/advanced-development/data-models/soft-deletable/page.mdx b/www/apps/book/app/advanced-development/data-models/soft-deletable/page.mdx
index 1728408a69..98060604a3 100644
--- a/www/apps/book/app/advanced-development/data-models/soft-deletable/page.mdx
+++ b/www/apps/book/app/advanced-development/data-models/soft-deletable/page.mdx
@@ -48,10 +48,12 @@ export default class MySoftDeletable {
---
-## Managing Soft-Deletable Models
+## Manage Soft-Deletable Models
Services extending the service factory have methods to soft delete and restore records for all models specified during its creation.
+### Soft Delete a Record
+
For example, to soft delete a `MySoftDeletable` record:
```ts
@@ -62,6 +64,25 @@ await helloModuleService.softDelete([
The method receives an array of IDs of records to delete.
+### Retrieve Soft-Deleted Records
+
+The `retrieve`, `list`, and `listAndCount` methods accept as a second parameter a configuration object.
+
+To retrieve soft-deleted records, set `withDeleted` to `true` in the configuration object passed as a second parameter.
+
+For example:
+
+```ts
+const deletedRecords = await helloModuleService
+ .listMySoftDeletables({
+ // ...
+ }, {
+ withDeleted: true
+ })
+```
+
+### Restore a Soft-Deleted Record
+
To restore a `MySoftDeletable` record:
```ts
@@ -72,4 +93,4 @@ await helloModuleService.restore([
The method also receives an array of IDs of records to restore.
-If the data model isn't the main data model, its method names are `softDelete` and `restore` suffixed with the plural name of the model. For example, `softDeleteMySoftDeletable`.
\ No newline at end of file
+If the data model isn't the main data model, its method names are `softDelete` and `restore` suffixed with the plural name of the model. For example, `softDeleteMySoftDeletable`.
diff --git a/www/apps/book/app/advanced-development/events-and-subscribers/data-payload/page.mdx b/www/apps/book/app/advanced-development/events-and-subscribers/data-payload/page.mdx
index e4d884506f..bfa0ffd09d 100644
--- a/www/apps/book/app/advanced-development/events-and-subscribers/data-payload/page.mdx
+++ b/www/apps/book/app/advanced-development/events-and-subscribers/data-payload/page.mdx
@@ -39,6 +39,8 @@ export const config: SubscriberConfig = {
This logs the product ID received in the `ProductService.Events.CREATED` (`product-created`) event’s data payload to the console.
-## List of Events with Data Payload
+{/* TODO check first if the events list is still correct */}
-Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads.
\ No newline at end of file
+{/* ## List of Events with Data Payload
+
+Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads. */}
\ No newline at end of file
diff --git a/www/apps/book/app/advanced-development/loaders/outside-modules/page.mdx b/www/apps/book/app/advanced-development/loaders/outside-modules/page.mdx
index 99c9e6a5c3..006865d120 100644
--- a/www/apps/book/app/advanced-development/loaders/outside-modules/page.mdx
+++ b/www/apps/book/app/advanced-development/loaders/outside-modules/page.mdx
@@ -8,13 +8,13 @@ In this chapter, you’ll learn how to create a loader outside a module.
## Loaders in the Medusa Application
-In your Medusa application, you can create loaders under the `src/loaders` directory. The Medusa application runs these loaders on start-up.
+In your Medusa application, you can create loaders under the `src/loaders` directory outside a module. The Medusa application runs these loaders on start-up.
This is useful if you’re performing a task on application start-up, but don’t need to define it in a module.
---
-## How to Create a Loader Outside Module?
+## How to Create a Loader Outside a Module?
To create a loader in your Medusa application outside a module, create a TypeScript or JavaScript file under the `src/loaders` directory that default exports a function.
diff --git a/www/apps/book/app/advanced-development/modules/connection-loader/page.mdx b/www/apps/book/app/advanced-development/modules/connection-loader/page.mdx
index 0c78fe64c0..7204190b89 100644
--- a/www/apps/book/app/advanced-development/modules/connection-loader/page.mdx
+++ b/www/apps/book/app/advanced-development/modules/connection-loader/page.mdx
@@ -6,19 +6,11 @@ export const metadata = {
In this document, you’ll learn about how to load the database connection in your module.
-## Modules Database Connection
-
-By default, a module will use the same database connection of the Medusa application.
-
-To access those database credentials and ensure that MikroORM’s [entity](https://mikro-orm.io/docs/entity-manager) and [transaction](https://mikro-orm.io/docs/transactions) managers use those credentials when performing database operations, you must pass a database connection loader in your module’s definition.
-
-Medusa provides a utility connection loader factory that handles registering the database options and entity and transaction managers into the module’s container.
-
----
-
## How to Add a Database Connection Loader?
-In your module’s definition file, import the `ModulesSdkUtils` utility function from `@medusajs/utils`. Then, use its `mikroOrmConnectionLoaderFactory` function to create a connection loader and export it in the module’s definition.
+To ensure that your module's database operations use the Medusa application's database connection, export a database connection loader in your module's definition.
+
+Medusa provides a utility function that creates the database connection loader for you.
For example:
diff --git a/www/apps/book/app/advanced-development/modules/container/page.mdx b/www/apps/book/app/advanced-development/modules/container/page.mdx
index b1cf0b46ed..c8aa9ec6e3 100644
--- a/www/apps/book/app/advanced-development/modules/container/page.mdx
+++ b/www/apps/book/app/advanced-development/modules/container/page.mdx
@@ -4,25 +4,28 @@ export const metadata = {
# {metadata.title}
-In this chapter, you'll learn about a module's container and how to register resources in that container.
+In this chapter, you'll learn about the module's container and how to register resources in that container.
## Module's Container
Each module has a local container only used by the resources of that module.
-So, resources in the module, such as services or loaders, can only resolve resources registered in the module's container.
-
-To register a module's resources in that container, Medusa provides a utility loader factory function that registers your module's services and other resources in the module's container.
+So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
---
-## How to Register a Modules Resources
+## The Container Loader
-In your module's definition file, import the `ModulesSdkUtils` utility functions from the `@medusajs/utils`. Then, use its `moduleContainerLoaderFactory` function to create the container loader and export it in the module's definition.
+Medusa provides a utility function that creates a container loader. This loader takes care of registering resources in your container.
For example:
-```ts title="src/modules/hello/index.ts"
+export const highlights = [
+ ["11", "moduleContainerLoaderFactory", "Create the container loader."],
+ ["25", "", "Export the container loader."]
+]
+
+```ts title="src/modules/hello/index.ts" highlights={highlights}
// other imports...
import HelloModuleService from "./service"
import { MyCustom } from "./models/my-custom"
@@ -51,13 +54,15 @@ export default {
}
```
-### Container Loader Factory Parameters
+You create the container loader using the utility function `moduleContainerLoaderFactory` and export it in the module's definition.
+
+### moduleContainerLoaderFactory Parameters
The `moduleContainerLoaderFactory` function accepts as a parameter an object with the following properties:
-- `moduleModels`: An object of the module's data models, where the key is the model's name and the value is the model class.
+- `moduleModels`: An object where each key is a data model's name, and its value the data model class.
- `moduleRepositories`: An object of the module's repositories. You import here the `MikroOrmBaseRepository` from `@medusajs/utils` and use it as the value of `BaseRepository`.
-- `moduleServices`: An object of the module's services, where the key is the service's registration name and the value is the service class.
+- `moduleServices`: An object where each key is the service's registration name, and its value is the service class.
### Resources Registered by the Container Loader
@@ -77,19 +82,21 @@ The container loader registers in the module's container:
A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container.
-For example, to resolve the `baseRepository` registered by the `containerLoader` in the module's definition:
+For example:
```ts
-import { DAL } from "@medusajs/types"
+import { ModulesSdkTypes } from "@medusajs/types"
+import { MyCustom } from "./models/my-custom"
type InjectedDependencies = {
- baseRepository: DAL.RepositoryService
+ myCustomService: ModulesSdkTypes.InternalModuleService
}
export default class HelloModuleService {
- protected baseRepository_: DAL.RepositoryService
- constructor({ baseRepository }: InjectedDependencies) {
- this.baseRepository_ = baseRepository
+ protected myCustomService_:
+ ModulesSdkTypes.InternalModuleService
+ constructor({ myCustomService }: InjectedDependencies) {
+ this.myCustomService_ = myCustomService
}
// ...
diff --git a/www/apps/book/app/advanced-development/modules/database-operations-in-services/page.mdx b/www/apps/book/app/advanced-development/modules/database-operations-in-services/page.mdx
index 4238c5e522..897befcb5a 100644
--- a/www/apps/book/app/advanced-development/modules/database-operations-in-services/page.mdx
+++ b/www/apps/book/app/advanced-development/modules/database-operations-in-services/page.mdx
@@ -4,68 +4,19 @@ export const metadata = {
# {metadata.title}
-In this document, you’ll learn how to implement database operations, such as creating a record, in a service’s methods.
+In this document, you’ll learn how to implement database operations, such as creating a record, in the main service.
-## Transaction Manager
+## Use Data Model Services
-A transaction wraps a set of database operations to ensure that when an error occurs, all database changes made by the executed operations are rolled back.
-
-When you implement a method in your service, you must add the `@InjectTransactionManager` decorator to the method performing database operations. For example:
-
-```ts title="src/modules/hello/service.ts" highlights={[["26"]]}
-// other imports
-import { InjectTransactionManager } from "@medusajs/utils"
-import { DAL } from "@medusajs/types"
-
-type InjectedDependencies = {
- baseRepository: DAL.RepositoryService
- myCustomService: ModulesSdkTypes.InternalModuleService
-}
-
-class HelloModuleService extends ModulesSdkUtils
- .abstractModuleServiceFactory<
- // ...
- >(
- // ...
- ) {
- protected baseRepository_: DAL.RepositoryService
-
- constructor(
- { baseRepository }: InjectedDependencies
- ) {
- // @ts-ignore
- super(...arguments)
- this.baseRepository_ = baseRepository
- }
-
- @InjectTransactionManager("baseRepository_")
- async create(
- // TODO add parameters
- ) {
- // TODO add implementation
- }
-}
-```
-
-The `@InjectTransactionManager` decorator accepts as a parameter the name of the service class field to inject the transaction manager to, which is the `baseRepository_` field.
-
----
-
-## Generated Service Methods
-
-As mentioned in a previous chapter, when you use the container loader factory to register resources in your module’s container, the factory creates a service for each data model you specify and registers it in the container. The registration name is the camel-case name of the data model with `Service` appended to it.
-
-So, the container loader factory creates a `myCustomService` for the `MyCustom` data model.
-
-These generated services already implement data-management methods such as `create` or `update`. So, in your module’s service, you can resolve the generated service and use it to create a record.
+The module container has a generated service registered for each data model. You can resolve that service and use it to perform database operations on the data model.
For example:
export const highlights = [
["13", "", "Inject myCustomService, which is the service generated by the container loader for the MyCustom data model."],
- ["23", "", "Add a new field for the generated service of the MyCustom data model."],
- ["31", "", "Set the class field to the injected dependency."],
- ["39", "create", "Use the `create` method of the generated service."]
+ ["22", "", "Add a new field for the generated service of the MyCustom data model."],
+ ["29", "", "Set the class field to the injected dependency."],
+ ["35", "create", "Use the `create` method of the generated service."]
]
```ts title="src/modules/hello/service.ts" highlights={highlights}
@@ -75,12 +26,12 @@ import { MyCustom } from "./models/my-custom"
// ...
+// recommended to define type in another file
type CreateMyCustomDTO = {
name: string
}
type InjectedDependencies = {
- baseRepository: DAL.RepositoryService
myCustomService: ModulesSdkTypes.InternalModuleService
}
@@ -90,22 +41,18 @@ class HelloModuleService extends ModulesSdkUtils
>(
// ...
) {
- protected baseRepository_: DAL.RepositoryService
protected myCustomService_: ModulesSdkTypes.InternalModuleService
constructor(
- { baseRepository, myCustomService }: InjectedDependencies
+ { myCustomService }: InjectedDependencies
) {
// @ts-ignore
super(...arguments)
- this.baseRepository_ = baseRepository
this.myCustomService_ = myCustomService
}
- @InjectTransactionManager("baseRepository_")
async create(
- data: CreateMyCustomDTO,
- @MedusaContext() context: Context = {}
+ data: CreateMyCustomDTO
): Promise {
const myCustom = await this.myCustomService_.create(
data,
@@ -117,6 +64,6 @@ class HelloModuleService extends ModulesSdkUtils
}
```
-In the above example, you add `myCustomService` to the `InjectedDependencies` type to resolve the service in the constructor and set it in a class field.
+In the above example, you resolve `myCustomService` in the main service's constructor. The `myCustomService` is the generated service for the `myCustom` data model.
-In the `create` method, you use `myCustomService`'s `create` method to create the record. You pass it the record’s data as a first parameter, and the Medusa application’s context as a second parameter.
+Then, in the `create` method of the main service, you use `myCustomService`'s `create` method to create the record.
diff --git a/www/apps/book/app/advanced-development/modules/example-crud-module-service/page.mdx b/www/apps/book/app/advanced-development/modules/example-crud-module-service/page.mdx
deleted file mode 100644
index 1800fb03ae..0000000000
--- a/www/apps/book/app/advanced-development/modules/example-crud-module-service/page.mdx
+++ /dev/null
@@ -1,411 +0,0 @@
-import { Tabs, TabsList, TabsTrigger, TabsContent, TabsContentWrapper, Badge } from "docs-ui"
-
-export const metadata = {
- title: `${pageNumber} Example: CRUD Module Service`,
-}
-
-# {metadata.title}
-
-In this chapter, you’ll find an example of how to implement CRUD operations in your module’s service and test it out with an API route.
-
-
-
-The example in this chapter uses the same `hello` module with the `HelloModuleService` and `MyCustom` data model from previous chapters.
-
-
-
-## 1. Create Types File
-
-Create the file `src/types/hello/my-custom.ts` with the following content:
-
-```ts title="src/types/hello/my-custom.ts"
-export type MyCustomDTO = {
- id: string
- name: string
-}
-
-export type CreateMyCustomDTO = {
- name: string
-}
-
-export type UpdateMyCustomDTO = {
- name?: string
-}
-
-```
-
-You’ll be using these types in the service.
-
----
-
-## 2. Create HelloModuleService
-
-Create or change the service at `src/modules/hello/service.ts` with the following content:
-
-```ts title="src/modules/hello/service.ts"
-import {
- ModulesSdkUtils,
- InjectTransactionManager,
- MedusaContext,
-} from "@medusajs/utils"
-import {
- DAL,
- ModulesSdkTypes,
- Context,
-} from "@medusajs/types"
-import { MyCustom } from "./models/my-custom"
-import {
- CreateMyCustomDTO,
- MyCustomDTO,
- UpdateMyCustomDTO,
-} from "../../types/hello/my-custom"
-
-type InjectedDependencies = {
- baseRepository: DAL.RepositoryService
- myCustomService: ModulesSdkTypes.InternalModuleService
-}
-
-type AllModelsDTO = {
- MyCustom: {
- dto: MyCustomDTO
- }
-}
-
-class HelloModuleService extends ModulesSdkUtils
- .abstractModuleServiceFactory<
- InjectedDependencies,
- MyCustomDTO,
- AllModelsDTO
- >(MyCustom, []) {
- protected baseRepository_: DAL.RepositoryService
- protected myCustomService_: ModulesSdkTypes.InternalModuleService
-
- constructor(
- { baseRepository, myCustomService }: InjectedDependencies
- ) {
- // @ts-ignore
- super(...arguments)
- this.baseRepository_ = baseRepository
- this.myCustomService_ = myCustomService
- }
-
- @InjectTransactionManager("baseRepository_")
- async create(
- data: CreateMyCustomDTO,
- @MedusaContext() context: Context = {}
- ): Promise {
- const myCustom = await this.myCustomService_.create(
- data,
- context
- )
-
- return myCustom
- }
-
- @InjectTransactionManager("baseRepository_")
- async update(
- id: string,
- data: UpdateMyCustomDTO,
- @MedusaContext() context: Context = {}
- ): Promise {
- const myCustom = await this.myCustomService_.update({
- ...data,
- id,
- })
-
- return myCustom
- }
- }
-
-export default HelloModuleService
-```
-
-This creates a `HelloModuleService` that uses the service factory to generate data-management methods like `list`, `retrieve`, and `delete`.
-
-
-
-Refer to the full list of generated methods in [this chapter](../service-factory/page.mdx#generated-methods).
-
-
-
-In the service, you also implement the `create` and `update` methods that create and update a `MyCustom` record respectively.
-
----
-
-## 3. Configure Module Definition
-
-Make sure that the module definition at `src/modules/hello/index.ts` uses the container and connection loaders explained in previous chapters:
-
-```ts title="src/modules/hello/index.ts"
-import HelloModuleService from "./service"
-import { ModulesSdkUtils, MikroOrmBaseRepository } from "@medusajs/utils"
-import { MyCustom } from "./models/my-custom"
-
-const moduleName = "hello"
-const pathToMigrations = __dirname + "/migrations"
-
-const migrationScriptOptions = {
- moduleName,
- models: {
- MyCustom,
- },
- pathToMigrations,
-}
-
-export const runMigrations = ModulesSdkUtils
- .buildMigrationScript(
- migrationScriptOptions
- )
-
-export const revertMigration = ModulesSdkUtils
- .buildRevertMigrationScript(
- migrationScriptOptions
- )
-
-const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
- moduleModels: {
- MyCustom,
- },
- moduleRepositories: {
- BaseRepository: MikroOrmBaseRepository,
- },
- moduleServices: [HelloModuleService],
-})
-
-const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
- moduleName,
- moduleModels: [MyCustom],
- migrationsPath: pathToMigrations,
-})
-
-export default {
- service: HelloModuleService,
- runMigrations,
- revertMigration,
- loaders: [containerLoader, connectionLoader],
-}
-```
-
-
-
-If you don't have the `MyCustom` data model, refer to [this chapter](../../../basics/data-models/page.mdx).
-
-
-
-Also, make sure that the module is added in the `modules` object defined in `medusa-config.js`:
-
-```js title="medusa-config.js"
-const modules = {
- helloModuleService: {
- resolve: "./dist/modules/hello",
- },
- // ...
-}
-```
-
----
-
-## 4. Add List and Create API Routes
-
-Create the file `src/api/store/custom/route.ts` with the following content:
-
-```ts title="src/api/store/custom/route.ts"
-import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
-import HelloModuleService from "../../../modules/hello/service"
-import { CreateMyCustomDTO } from "../../../types/hello/my-custom"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const helloModuleService: HelloModuleService =
- req.scope.resolve(
- "helloModuleService"
- )
-
- res.json({
- my_customs: await helloModuleService.list(),
- })
-}
-
-type CustomReq = CreateMyCustomDTO
-
-export async function POST(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const helloModuleService: HelloModuleService =
- req.scope.resolve(
- "helloModuleService"
- )
-
- // skipping validation for simplicity
- const myCustom = await helloModuleService.create(req.body)
-
- res.json({
- my_custom: myCustom,
- })
-}
-
-```
-
-This adds two API routes:
-
-1. A `GET` API route at `/store/custom` that retrieves a list of `MyCustom` records.
-2. A `POST` API route at `/store/custom` that creates a `MyCustom` record.
-
----
-
-## 5. Add Retrieve, Update, and Delete API Routes
-
-Create the file `src/api/store/custom/[id]/route.ts` with the following content:
-
-```ts title="src/api/store/custom/[id]/route.ts"
-import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
-import HelloModuleService from "../../../../modules/hello/service"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const helloModuleService: HelloModuleService =
- req.scope.resolve(
- "helloModuleService"
- )
-
- const myCustom = await helloModuleService.retrieve(req.params.id)
-
- res.json({
- my_custom: myCustom,
- })
-}
-
-export async function POST(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const helloModuleService: HelloModuleService =
- req.scope.resolve(
- "helloModuleService"
- )
-
- // skipping validation for simplicity
- const myCustom = await helloModuleService.update(
- req.params.id,
- req.body
- )
-
- res.json({
- my_custom: myCustom,
- })
-}
-
-export async function DELETE(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const helloModuleService: HelloModuleService =
- req.scope.resolve(
- "helloModuleService"
- )
-
- await helloModuleService.delete(req.params.id)
-
- res.status(200).json({
- success: true,
- })
-}
-```
-
-This creates three API routes:
-
-1. A `GET` API route at `/store/custom/[id]` that retrieves a `MyCustom` record by its ID.
-2. A `POST` API route at `/store/custom/[id]` that updates a `MyCustom` record.
-3. A `DELETE` API route at `/store/custom/[id]` that deletes a `MyCustom` record.
-
----
-
-## 6. Test it Out
-
-Start your Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, test out each of the API routes you created.
-
-
-
-
-
- GET /store/custom
-
-
-
-
- POST /store/custom
-
-
-
-
- GET /store/custom/[id]
-
-
-
-
- POST /store/custom/[id]
-
-
-
-
- DEL /store/custom/[id]
-
-
-
-
-
-
- ```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/custom"
- curl http://localhost:9000/store/custom
- ```
-
-
-
-
- ```bash apiTesting testApiMethod="POST" testApiUrl="http://localhost:9000/store/custom" testBodyParams={{ "name": "test" }}
- curl -X POST http://localhost:9000/store/custom \
- --header 'Content-Type: application/json' \
- --data-raw '{
- "name": "test"
- }'
- ```
-
-
-
-
- ```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/custom/{id}" testPathParams={{ "id": "mc_123" }}
- curl http://localhost:9000/store/custom/mc_123
- ```
-
-
-
-
- ```bash apiTesting testApiMethod="POST" testApiUrl="http://localhost:9000/store/custom/{id}" testPathParams={{ "id": "mc_123" }} testBodyParams={{ "name": "test" }}
- curl -X POST http://localhost:9000/store/custom/mc_123 \
- --header 'Content-Type: application/json' \
- --data-raw '{
- "name": "test"
- }'
- ```
-
-
-
-
- ```bash apiTesting testApiMethod="DELETE" testApiUrl="http://localhost:9000/store/custom/{id}" testPathParams={{ "id": "mc_123" }}
- curl -X DELETE http://localhost:9000/store/custom/mc_123
- ```
-
-
-
-
diff --git a/www/apps/book/app/advanced-development/modules/example-module-relationship/page.mdx b/www/apps/book/app/advanced-development/modules/example-module-relationship/page.mdx
deleted file mode 100644
index 3338bed890..0000000000
--- a/www/apps/book/app/advanced-development/modules/example-module-relationship/page.mdx
+++ /dev/null
@@ -1,431 +0,0 @@
-export const metadata = {
- title: `${pageNumber} Example: Module Relationship`,
-}
-
-# {metadata.title}
-
-This chapter gives an example of adding a relationship from your custom module to the Product Module.
-
-
-
-The example in this chapter uses the same `hello` module with the `HelloModuleService` from previous chapters.
-
-
-
-## Create CustomProductData Data Model
-
-Create the file `src/modules/hello/models/custom-product-data.ts` with the following content:
-
-```ts title="src/modules/hello/models/custom-product-data.ts"
-import { generateEntityId } from "@medusajs/utils"
-import {
- BeforeCreate,
- Entity,
- OnInit,
- PrimaryKey,
- Property,
-} from "@mikro-orm/core"
-
-@Entity()
-export class CustomProductData {
- @PrimaryKey({ columnType: "text" })
- id!: string
-
- @Property({ columnType: "text" })
- custom_field: string
-
- @Property({ columnType: "text", nullable: true })
- product_id?: string
-
- @BeforeCreate()
- onCreate() {
- this.id = generateEntityId(this.id, "cpd")
- }
-
- @OnInit()
- OnInit() {
- this.id = generateEntityId(this.id, "cpd")
- }
-}
-```
-
-This creates a new data model `CustomProductData` to the `hello` module that stores custom fields related to the `Product` data model in the Product Module
-
-### Add Data Model in MikroORM Configurations
-
-Add your new data model to the MikroORM configurations file at `src/modules/hello/mikro-orm.config.dev.ts`:
-
-```ts title="src/modules/hello/mikro-orm.config.dev.ts"
-// other imports...
-import { CustomProductData } from "./models/custom-product-data"
-
-module.exports = {
- entities: [
- // other data models...
- CustomProductData,
- ],
- // ...
-}
-```
-
-### Add Data Model to Module Definition
-
-In your module definition at `src/modules/hello/index.ts`, add the data model to the `models` object:
-
-```ts title="src/modules/hello/index.ts"
-// other imports...
-import { CustomProductData } from "./models/custom-product-data"
-
-const models = {
- // other models...
- CustomProductData,
-}
-
-// ...
-
-const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
- moduleModels: models,
- // ...
-})
-
-const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
- moduleModels: Object.values(models),
- // ...
-})
-
-// ...
-```
-
-This ensures that the model is passed to the container and connection loader along with other models.
-
-### Add Data Model Alias in Joiner Configurations
-
-Add an alias for the data model in the joiner configurations defined in `src/modules/hello/joiner-config.ts`:
-
-```ts title="src/modules/hello/joiner-config.ts"
-// other imports...
-import { ModuleJoinerConfig } from "@medusajs/types"
-import { CustomProductData } from "./models/custom-product-data"
-
-const joinerConfig: ModuleJoinerConfig = {
- // ...,
- alias: [
- // other aliases
- {
- name: ["custom_product_data"],
- args: {
- entity: CustomProductData.name,
- methodSuffix: "CustomProductDatas",
- },
- },
- ],
-}
-```
-
-### Create Types
-
-Create the file `src/types/hello/custom-product-data.ts` with the following types:
-
-```ts title="src/types/hello/custom-product-data.ts"
-export type CustomProductDataDTO = {
- id: string
- custom_field: string
- product_id?: string
-}
-
-export type CreateCustomProductDataDTO = {
- custom_field: string
- product_id?: string
-}
-
-export type UpdateCustomProductDataDTO = {
- custom_field?: string
- product_id?: string
-}
-
-```
-
-You’ll use those in the service next.
-
-### Add Data Model to Main Module Service
-
-In the main module service defined at `src/modules/hello/service.ts`, make the following changes to generate and add methods for the new `CustomProductData` model:
-
-export const serviceHighlights = [
- ["10", "customProductDataService", "Inject the generated service for the `CustomProductData` data model."],
- ["16", "CustomProductData", "Specify the expected input/output type of the data model."],
- ["23", "CustomProductData", "Specify the `CustomProductData` data model to generate methods for it."],
- ["49", "", "Set the `customProductDataService_` class field to the injected `customProductDataService` service."],
- ["55", "createCustomProductData", "Add a method that creates a `CustomProductData` record."]
-]
-
-```ts title="src/modules/hello/service.ts" highlights={serviceHighlights}
-// other imports...
-import { CustomProductData } from "./models/custom-product-data"
-import {
- CreateCustomProductDataDTO,
- CustomProductDataDTO,
-} from "../../types/hello/custom-product-data"
-
-type InjectedDependencies = {
- // other injected dependencies...
- customProductDataService:
- ModulesSdkTypes.InternalModuleService
-}
-
-type AllModelsDTO = {
- // other model DTOs...
- CustomProductData: {
- dto: CustomProductDataDTO
- }
-}
-
-const generateMethodsFor = [
- // other data models generating methods for...
- CustomProductData,
-]
-
-// ...
-
-class HelloModuleService extends ModulesSdkUtils
- .abstractModuleServiceFactory<
- InjectedDependencies,
- MyCustomDTO,
- AllModelsDTO
- >(MyCustom, generateMethodsFor) {
- // ...
- protected customProductDataService_:
- ModulesSdkTypes.InternalModuleService
-
- constructor(
- {
- // other injected dependencies...
- customProductDataService,
- }: InjectedDependencies,
- protected readonly moduleDeclaration:
- InternalModuleDeclaration
- ) {
- // @ts-ignore
- super(...arguments)
- // ...
- this.customProductDataService_ = customProductDataService
- }
-
- // other methods...
-
- @InjectTransactionManager("baseRepository_")
- async createCustomProductData(
- data: CreateCustomProductDataDTO,
- @MedusaContext() context: Context = {}
- ): Promise {
- const customProductData = await this.customProductDataService_.create(
- data,
- context
- )
-
- return this.baseRepository_.serialize(
- customProductData
- )
- }
- }
-```
-
-In the main service you make the following changes:
-
-1. Add `customProductDataService` to the list of injected dependencies.
-2. Add the expected input/output type of `CustomProductData` into the `AllModelsDTO` type, which is passed as the third-argument to the `abstractModuleServiceFactory` function.
-3. Add the data model to the `generateMethodsFor` array, which is passed as a second parameter to the `abstractModuleServiceFactory` function.
-4. Add a `customProductDataService_` field to the `HelloModuleService` and set its value in the constructor to the injected `customProductDataService`.
-5. Create a new method `createCustomProductData` that creates a `CustomProductData` record.
-
-### Generate Migration
-
-Use the following command to generate the migration file:
-
-```bash
-npx cross-env MIKRO_ORM_CLI=./src/modules/hello/mikro-orm.config.dev.ts mikro-orm migration:create
-```
-
-### Run Migration
-
-Run the following commands to run the latest migration:
-
-```bash npm2yarn
-npm run build
-npx medusa migrations run
-```
-
----
-
-## Add Relationship
-
-To add the relationship from the `hello` module to the Product Module, make the following change in `src/modules/hello/joiner-config.ts`:
-
-```ts title="src/modules/hello/joiner-config.ts"
-// other imports...
-import { ModuleJoinerConfig } from "@medusajs/types"
-import { Modules } from "@medusajs/modules-sdk"
-
-const joinerConfig: ModuleJoinerConfig = {
- // ...
- relationships: [
- {
- serviceName: Modules.PRODUCT,
- primaryKey: "id",
- foreignKey: "product_id",
- alias: "product",
- },
- ],
-}
-```
-
-This adds a new relationship in the joiner configuration of the `hello` module. The relationship references the `product` alias in the Product Module, which is the alias of the `Product` data model in that module.
-
-The `product_id` field in your module’s data models references the `id` field of the `Product` data model.
-
----
-
-## Implement Create API Route
-
-In this section, you’ll implement an API route that creates a product (for simplicity) and then creates a `CustomProductData` record that references that product.
-
-
-
-This example creates a product in a store route to simplify testing the relationship. In a realistic use case, only create products under the `/admin` prefix to ensure that the user is an authenticated admin.
-
-
-
-Create the file `src/api/store/custom-product-data/route.ts` with the following content:
-
-export const apiRouteHighlights = [
- ["37", "remoteQuery", "Resolve the remote query function from the Medusa container."],
- ["44", "", "Create a product using the parameters in the request body."],
- ["48", "", "Create a `CustomProductData` record with a reference to the created product's ID."],
- ["54", "remoteQueryObjectFromString", "Create the query to pass to the remote query function."],
- ["59", '"product.title"', "Retrieve the product title among the `CustomProductData` record's data."],
- ["60", '"product.handle"', "Retrieve the product handle among the `CustomProductData` record's data."],
- ["63", "", "Filter fetched records by the ID of the created record."],
- ["67", "", "Run the query with the remote query function."],
- ["70", "", "Return the created `CustomProductData` record in the response."]
-]
-
-```ts title="src/api/store/custom-product-data/route.ts" highlights={apiRouteHighlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
-import {
- CreateCustomProductDataDTO,
-} from "../../../types/hello/custom-product-data"
-import HelloModuleService from "../../../modules/hello/service"
-import type {
- IProductModuleService,
- CreateProductDTO,
-} from "@medusajs/types"
-import {
- remoteQueryObjectFromString,
- ContainerRegistrationKeys,
-} from "@medusajs/utils"
-import type {
- RemoteQueryFunction,
-} from "@medusajs/modules-sdk"
-
-type CreateCustomProductDataReq =
- CreateCustomProductDataDTO & {
- product_data: CreateProductDTO
- }
-
-export async function POST(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const helloModuleService: HelloModuleService =
- req.scope.resolve(
- "helloModuleService"
- )
-
- const productModuleService: IProductModuleService =
- req.scope.resolve(
- "productModuleService"
- )
-
- const remoteQuery: RemoteQueryFunction = req.scope.resolve(
- ContainerRegistrationKeys.REMOTE_QUERY
- )
-
- // skipping validation for simplicity
- const { product_data: productData, ...rest } = req.body
-
- const product = await productModuleService.create(
- productData as CreateProductDTO
- )
-
- const customProductData = await helloModuleService
- .createCustomProductData({
- ...rest,
- product_id: product.id,
- })
-
- const query = remoteQueryObjectFromString({
- entryPoint: "custom_product_data",
- fields: [
- "id",
- "custom_field",
- "product.title",
- "product.handle",
- ],
- variables: {
- id: customProductData.id,
- },
- })
-
- const results = await remoteQuery(query)
-
- res.json({
- custom_product_data: results[0],
- })
-}
-```
-
-This API route accepts the necessary request body parameters to create a `CustomProductData` record. It also accepts a `product_data` request body parameter to create the product to be referenced by the `CustomProductData` record.
-
-In the API route handler, you create the product and then create the `CustomProductData` record.
-
-Then, you use the remote query function, resolved from the Medusa container, to fetch the created `CustomProductData` record (filtering by its ID) with the product it references. You can add more product-related fields to the `fields` array passed to `remoteQueryObjectFromString`.
-
-Finally, you return the data returned by the `remoteQuery` function.
-
-### Test Create API Route
-
-To test the API route, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, send a `POST` request to the `/store/custom-product-data` route:
-
-```bash
-curl --location 'http://localhost:9000/store/custom-product-data' \
---header 'Content-Type: application/json' \
---data '{
- "product_data": {
- "title": "Pants"
- },
- "custom_field": "nice shirt"
-}'
-```
-
-You’ll receive the following response:
-
-```json
-{
- "custom_product_data": {
- "id": "cpd_01HWWDD260T0CS64WAF8CVHQ9Y",
- "custom_field": "nice shirt",
- "product_id": "prod_01HWWDD25VPQNNZJ57PFTX2JHE",
- "product": {
- "title": "Shoes",
- "handle": "shoes",
- "id": "prod_01HWWDD25VPQNNZJ57PFTX2JHE"
- }
- }
-}
-```
\ No newline at end of file
diff --git a/www/apps/book/app/advanced-development/modules/link-modules/page.mdx b/www/apps/book/app/advanced-development/modules/link-modules/page.mdx
index fd5321c466..f8ad859e1b 100644
--- a/www/apps/book/app/advanced-development/modules/link-modules/page.mdx
+++ b/www/apps/book/app/advanced-development/modules/link-modules/page.mdx
@@ -101,7 +101,7 @@ await remoteLink.dismiss({
})
```
-The `dismiss` method accepts the same parameter type as the create method.
+The `dismiss` method accepts the same parameter type as the [create method](#create-link).
### Cascade Delete Linked Records
diff --git a/www/apps/book/app/advanced-development/modules/module-relationships/page.mdx b/www/apps/book/app/advanced-development/modules/module-relationships/page.mdx
index 7180d0bcab..06b51ffdcc 100644
--- a/www/apps/book/app/advanced-development/modules/module-relationships/page.mdx
+++ b/www/apps/book/app/advanced-development/modules/module-relationships/page.mdx
@@ -6,13 +6,13 @@ export const metadata = {
# {metadata.title}
-In this document, you’ll learn about creating relationships in queryable modules.
+In this document, you’ll learn about creating relationships between modules.
-## What is a Relationship in a Queryable Module?
+## What is a Module Relationship?
-A queryable module can have a relationship to another queryable module in the form of a reference.
+A module can have a relationship to another module in the form of a reference.
-The Medusa application resolves these relationships while maintaining isolation between the modules and allowing you to retrieve data across them using the remote query.
+The Medusa application resolves these relationships while maintaining isolation between the modules and allowing you to retrieve data across them.
@@ -31,15 +31,16 @@ The Medusa application resolves these relationships while maintaining isolation
## How to Create a Module Relationship?
-
+
-The next chapter provides a detailed example of implementing a relationship between two modules. This section gives the general steps to creating the relationship.
+1. Define a `__joinerConfig` method in the module's main service.
+2. Configure module to be queryable.
Consider you’re creating a data model that adds custom fields associated with a product:
-```ts title="src/modules/hello/models/another-custom.ts" highlights={[["13"]]}
+```ts title="src/modules/hello/models/custom-product-data.ts" highlights={[["19"]]}
import { generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
@@ -74,89 +75,203 @@ export class CustomProductData {
The `CustomProductData` data model has a `product_id` field to reference the product it adds custom fields for.
-To create a relationship to the `product` data model in the Product Module, add the following to the joiner configuration of your module:
+
+
+When you add a new data model, make sure to:
+
+- Create a migration for it.
+- Add it to the second parameter of the main service's factory function.
+- Add it to the container and connection loaders.
+
+
+
+### 1. Define `__joinerConfig` Method
+
+To create a relationship to the `product` data model of the Product Module, create a public `__joinerConfig` method in your main module's service:
export const relationshipsHighlight = [
- ["10", "serviceName", "The name of the module that this relationship is referencing."],
- ["11", "alias", "The alias of the data model you’re referencing in the other module."],
- ["12", "primaryKey", "The name of the field you’re referencing in the other module’s data model."],
- ["13", "foreignKey", "The name of the field in your data models referencing the other module’s model."],
+ ["39", "serviceName", "The name of the module that this relationship is referencing."],
+ ["40", "alias", "The alias of the data model you’re referencing in the other module."],
+ ["41", "primaryKey", "The name of the field you’re referencing in the other module’s data model."],
+ ["42", "foreignKey", "The name of the field in your data models referencing the other module’s model."],
]
-```ts title="src/modules/hello/joiner-config.ts" highlights={relationshipsHighlight}
+```ts title="src/modules/hello/service.ts" highlights={relationshipsHighlight}
// other imports...
+import { MyCustom } from "./models/custom-product-data"
+import { CustomProductData } from "./models/custom-product-data"
import { ModuleJoinerConfig } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
-import { CustomProductData } from "./models/custom-product-data"
-const joinerConfig: ModuleJoinerConfig = {
- // ...
- relationships: [
- {
- serviceName: Modules.PRODUCT,
- alias: "product",
- primaryKey: "id",
- foreignKey: "product_id",
- },
- ],
+class HelloModuleService extends ModulesSdkUtils
+ .abstractModuleServiceFactory<
+ // ...
+ >(
+ // ...
+ ) {
+
+ // ...
+
+ __joinerConfig(): ModuleJoinerConfig {
+ return {
+ serviceName: "helloModuleService",
+ primaryKeys: ["id"],
+ alias: [
+ {
+ name: ["my_custom"],
+ args: {
+ entity: MyCustom.name
+ }
+ },
+ {
+ name: ["custom_product_data"],
+ args: {
+ entity: CustomProductData.name,
+ // Only needed if data model isn't main data model
+ // of service
+ methodSuffix: "CustomProductDatas",
+ },
+ },
+ ],
+ relationships: [
+ {
+ serviceName: Modules.PRODUCT,
+ alias: "product",
+ primaryKey: "id",
+ foreignKey: "product_id",
+ },
+ ],
+ }
+ }
+
+ // ...
}
-
-export default joinerConfig
```
-You pass a new property `relationships` to the `joinerConfig`. This property’s value is an array of relationship definitions from your module to other modules.
+This creates a relationship to the `Product` data model of the Product Module using the alias `product`. The `product_id` fields in your data models are considered references to the `id` field of the `Product` data model.
-Each relationship definition object accepts the following properties:
+#### `__joinerConfig` Return Type
+]} sectionTitle="Define __joinerConfig Method" />
-So, the above example creates a relationship to the `Product` data model of the Product Module, which has the alias `product`. `product_id` fields in your module’s data models are considered references to the `id` field of the `Product` data model in the Product Module.
+### 2. Adjust Module Configuration
----
+To use relationships in a module, adjust its configuration object passed to the `modules` object in `medusa-config.js`:
-## Query Data Across Relationships
+export const configHighlights = [
+ ["7", "isQueryable", "Enable this property to use relationships in a module."]
+]
-The remote query allows you to query data across modules using their relationship without the actual dependency between the modules.
-
-To do that, specify within the `fields` retrieved in the query the referenced data model’s fields. For example:
-
-```ts highlights={[["3", '"product.title"', "Retrieve the referenced product's title."]]}
-const query = remoteQueryObjectFromString({
- entryPoint: "another_custom",
- fields: ["id", "custom_field", "product.title"],
-})
-
-const result = await remoteQuery(query)
+```js title="medusa-config.js" highlights={configHighlights}
+const modules = {
+ helloModuleService: {
+ // ...
+ definition: {
+ key: "helloModuleService",
+ registrationName: "helloModuleService",
+ isQueryable: true,
+ },
+ },
+ // ...
+}
```
-Use `alias`'s value in the relationship definition to reference the other module’s data model. To specify its fields, use dot notation.
+Enabling the `isQueryable` property is required to use relationships in a module.
-So, in the example above, the referenced product’s title is retrieved along with the ID under the `product` property of each record object in the result.
+The `definition` property’s value is an object that accepts the following properties, among others:
+
+
---
@@ -164,76 +279,39 @@ So, in the example above, the referenced product’s title is retrieved along wi
If the data model you’re referencing isn’t the main data model of the main module service, pass to the relationship definition the `args` property:
-```ts title="src/modules/hello/joiner-config.ts" highlights={[["10", "methodSuffix", "The suffix of the referenced data model's methods."]]}
-const joinerConfig: ModuleJoinerConfig = {
- // ...
- relationships: [
- {
- serviceName: Modules.PRODUCT,
- primaryKey: "id",
- foreignKey: "variant_id",
- alias: "variant",
- args: {
- methodSuffix: "Variants",
- },
- },
- ],
-}
+```ts title="src/modules/hello/service.ts" highlights={[["20", "methodSuffix", "The suffix of the referenced data model's methods."]]}
+class HelloModuleService extends ModulesSdkUtils
+ .abstractModuleServiceFactory<
+ // ...
+ >(
+ // ...
+ ) {
+
+ // ...
+
+ __joinerConfig(): ModuleJoinerConfig {
+ return {
+ // ...
+ relationships: [
+ {
+ serviceName: Modules.PRODUCT,
+ primaryKey: "id",
+ foreignKey: "variant_id",
+ alias: "variant",
+ args: {
+ methodSuffix: "Variants",
+ },
+ },
+ ],
+ }
+ }
+ }
```
The `args` property’s value is an object accepting a `methodSuffix` property. The `methodSuffix` property’s value is the plural name of the data model.
---
-## Inverse Relationship
+## Querying Module Relationships
-If you’re creating a relationship between two custom modules, you can define the relationship on the referenced side using the `extends` property of the joiner configuration.
-
-For example:
-
-
-
-
- ```ts title="src/modules/hello/joiner-config.ts"
- // Hello module
- const joinerConfig: ModuleJoinerConfig = {
- // ...
- relationships: [
- {
- serviceName: "anotherHelloModuleService",
- alias: "another_custom",
- primaryKey: "id",
- foreignKey: "another_custom_id",
- },
- ],
- }
- ```
-
-
-
-
- ```ts title="src/modules/another-hello/joiner-config.ts" highlights={[["6", "serviceName", "The name of the module this relationship was originally created in."], ["7", "relationship", "The relationship object as defined in the referencing module."]]}
- // Another hello module
- const joinerConfig: ModuleJoinerConfig = {
- // ...
- extends: [
- {
- serviceName: "helloModuleService",
- relationship: {
- serviceName: "anotherHelloModuleService",
- alias: "another_custom",
- primaryKey: "id",
- foreignKey: "another_custom_id",
- },
- },
- ],
- }
- ```
-
-
-
-
-The `extends` property’s value is an array of objects having the following properties:
-
-1. `serviceName`: The name of the module this relationship was originally created in.
-2. `relationship`: The relationship object as defined in the referencing module.
+The next chapter explains how to query data across module relationships.
diff --git a/www/apps/book/app/advanced-development/modules/options/page.mdx b/www/apps/book/app/advanced-development/modules/options/page.mdx
index 52b52781b1..5f141db243 100644
--- a/www/apps/book/app/advanced-development/modules/options/page.mdx
+++ b/www/apps/book/app/advanced-development/modules/options/page.mdx
@@ -6,7 +6,7 @@ export const metadata = {
# {metadata.title}
-In this chapter, you’ll learn about passing options to your Module from the Medusa application’s configurations and using them in the Module’s resources.
+In this chapter, you’ll learn about passing options to your module from the Medusa application’s configurations and using them in the module’s resources.
## What are Module Options?
@@ -14,8 +14,6 @@ A module can receive options to customize or configure its functionality.
For example, if you’re creating a module that integrates a third-party service, you’ll want to receive the integration credentials in the options rather than adding them directly in your code.
-A module can receive options when they’re added to the Medusa application’s configuration.
-
---
## How to Pass Options to a Module?
@@ -42,86 +40,47 @@ The `options` property’s value is an object. You can pass any properties you w
## Access Module Options in Main Service
-The module’s main service receives the module’s declaration as a second parameter. The module declaration is an object having an `options` property. Use it to access the options passed from the Module’s configurations.
+The module’s main service receives the module options as a second parameter.
For example:
-
-
+```ts title="src/modules/hello/service.ts" highlights={[["15"], ["21"], ["25"], ["26"], ["27"]]}
+// ...
- ```ts highlights={[["24"], ["28"], ["29"], ["30"]]}
- // other imports...
- import { InternalModuleDeclaration } from "@medusajs/types"
+// recommended to define type in another file
+type HelloModuleOptions = {
+ capitalize?: boolean
+}
+class HelloModuleService extends ModulesSdkUtils
+ .abstractModuleServiceFactory<
// ...
-
- // recommended to define type in another file
- type HelloModuleOptions = {
- capitalize?: boolean
- }
-
- class HelloModuleService extends ModulesSdkUtils
- .abstractModuleServiceFactory<
+ >(
+ // ...
+ ) {
// ...
- >(
- // ...
- ) {
- // ...
- protected options_: HelloModuleOptions
-
- constructor(
- {
- // ...
- }: InjectedDependencies,
- protected readonly moduleDeclaration: InternalModuleDeclaration
- ) {
- //...
-
- this.options_ = moduleDeclaration.options || {
- capitalize: false,
- }
- }
- }
- ```
-
-
-
-
- ```ts highlights={[["9"], ["13"], ["14"], ["16"], ["17"], ["18"]]}
- import { InternalModuleDeclaration } from "@medusajs/types"
-
- // recommended to define type in another file
- type HelloModuleOptions = {
- capitalize?: boolean
- }
-
- export default class HelloModuleService {
protected options_: HelloModuleOptions
-
+
constructor(
- {},
- protected readonly moduleDeclaration:
- InternalModuleDeclaration
+ {
+ // ...
+ }: InjectedDependencies,
+ protected readonly moduleOptions: HelloModuleOptions
) {
- this.options_ = moduleDeclaration.options || {
- capitalize: false,
+ //...
+
+ this.options_ = moduleOptions || {
+ capitalize: false
}
}
-
- getMessage() {
- return "Hello, world!"
- }
}
- ```
-
-
-
+```
---
## Access Module Options in Loader
-The object that a module’s loaders receive as a parameter has an `options` property. Use it to access the options passed from the Module’s configurations.
+The object that a module’s loaders receive as a parameter has an `options` property holding the module's options.
For example:
diff --git a/www/apps/book/app/advanced-development/modules/queryable-modules/page.mdx b/www/apps/book/app/advanced-development/modules/queryable-modules/page.mdx
deleted file mode 100644
index cc147a3b36..0000000000
--- a/www/apps/book/app/advanced-development/modules/queryable-modules/page.mdx
+++ /dev/null
@@ -1,178 +0,0 @@
-import { TypeList } from "docs-ui"
-
-export const metadata = {
- title: `${pageNumber} Queryable Modules`,
-}
-
-# {metadata.title}
-
-In this chapter, you’ll learn what a queryable module is and how to configure a module to be queryable.
-
-## What is a Queryable Module?
-
-A queryable module is one whose data can be queried more flexibly and referenced in other modules.
-
-By making a module queryable, you can fetch its data using object and GraphQL syntax without using the main service’s methods.
-
-
-
-- You want more flexibility when fetching data.
-- You’re creating relations between modules and querying data across modules.
-
-
-
----
-
-## How to Make a Module Queryable
-
-
-
-1. Configure module to be queryable.
-2. Add `__joinerConfig` method to main service.
-
-
-
-### 1. Adjust Module Configuration
-
-To make a module queryable, adjust its configuration object passed to the `modules` object in `medusa-config.js`. The configuration object accepts a `definition` property, whose value is an object of advanced module configurations.
-
-For example:
-
-export const configHighlights = [
- ["5", "key", "The module's key in the `modules` object."],
- ["6", "registrationName", "The name that the main service is registered under in the Medusa container. It’s recommended to be the same as `key`'s value."],
- ["7", "isQueryable", "Whether the module is queryable."]
-]
-
-```js title="medusa-config.js" highlights={configHighlights}
-const modules = {
- helloModuleService: {
- resolve: "./dist/modules/hello",
- definition: {
- key: "helloModuleService",
- registrationName: "helloModuleService",
- isQueryable: true,
- },
- },
- // ...
-}
-```
-
-The `definition` property’s value accepts the following properties, among others:
-
-
-
-### 2. Define `__joinerConfig` Method
-
-Queryable modules must define a public `__joinerConfig` method in their main service. This method returns an object that defines how a module’s data can be accessed and referenced.
-
-Create the file `src/modules/hello/joiner-config.ts` with the following content:
-
-```ts title="src/modules/hello/joiner-config.ts"
-import { ModuleJoinerConfig } from "@medusajs/types"
-import { MyCustom } from "./models/my-custom"
-
-const joinerConfig: ModuleJoinerConfig = {
- serviceName: "helloModuleService",
- primaryKeys: ["id"],
- alias: [
- {
- name: ["my_custom"],
- args: {
- entity: MyCustom.name,
- },
- },
- ],
-}
-
-export default joinerConfig
-```
-
-The `joinerConfig` object of type `ModuleJoinerConfig` imported from `@medusajs/types` accepts the following properties:
-
-
-
-Then, import that file in `src/modules/hello/service.ts` and add a new `__joinerConfig` method:
-
-```ts title="src/modules/hello/service.ts" highlights={[["3"], ["14"], ["15"], ["16"]]}
-// other imports...
-import { ModuleJoinerConfig } from "@medusajs/types"
-import joinerConfig from "./joiner-config"
-
-class HelloModuleService extends ModulesSdkUtils
- .abstractModuleServiceFactory<
- // ...
- >(
- // ...
- ) {
-
- // ...
-
- __joinerConfig(): ModuleJoinerConfig {
- return joinerConfig
- }
-
- // ...
-}
-```
-
----
-
-## Querying a Queryable Module
-
-The next chapter explains how to query the data of a Queryable module.
diff --git a/www/apps/book/app/advanced-development/modules/remote-query/page.mdx b/www/apps/book/app/advanced-development/modules/remote-query/page.mdx
index 7952c641f7..37aa337f50 100644
--- a/www/apps/book/app/advanced-development/modules/remote-query/page.mdx
+++ b/www/apps/book/app/advanced-development/modules/remote-query/page.mdx
@@ -10,7 +10,7 @@ In this chapter, you’ll learn about the remote query and how to use it to fetc
## What is the Remote Query?
-The remote query fetches data across queryable modules. It’s a function registered in the Medusa container under the `remoteQuery` key.
+The remote query fetches data across modules and their relationships having their `isQueryable` configuration enabled. It’s a function registered in the Medusa container under the `remoteQuery` key.
In your resources, such as API routes or workflows, you can resolve the remote query to fetch data across custom modules and Medusa’s commerce modules.
@@ -28,7 +28,7 @@ export const exampleHighlights = [
["27", "remoteQuery", "Run the query using the remote query."]
]
-```ts title="src/api/store/query/route.ts" highlights={exampleHighlights}
+```ts title="src/api/store/query/route.ts" highlights={exampleHighlights} apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/query"
import {
MedusaRequest,
MedusaResponse,
@@ -50,12 +50,12 @@ export async function GET(
)
const query = remoteQueryObjectFromString({
- entryPoint: "my_custom",
- fields: ["name", "id"],
+ entryPoint: "custom_product_data",
+ fields: ["id", "custom_field", "product.title"],
})
res.json({
- my_customs: await remoteQuery(query),
+ custom_product_data: await remoteQuery(query),
})
}
```
@@ -64,62 +64,24 @@ In the above example, you resolve `remoteQuery` from the Medusa container.
Then, you create a query using the `remoteQueryObjectFromString` utility function imported from `@medusajs/utils`. This function accepts as a parameter an object with the following required properties:
-- `entryPoint`: The alias name of the model you’re querying. In the previous chapter, you added to the joiner configurations of the module the alias `my_custom` for the `MyCustom` data model.
-- `fields`: An array of the data model’s field names to retrieve in the result.
+- `entryPoint`: The alias name of the model you’re querying. You defined the alias name in the `__joinerConfig` method of your main service.
+- `fields`: An array of the data model’s field names to retrieve in the result. You can also specify fields of a relationship using dot notation.
You then pass the query to the `remoteQuery` function to retrieve the results.
-### Test API Route
-
-To test out the API route, run the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, send a `GET` request to the `/store/query` API route:
-
-```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/query"
-curl http://localhost:9000/store/query
-```
-
-You’ll receive an array of `MyCustom` records under the `my_customs` key in the JSON response.
-
---
## Apply Filters
-The `remoteQueryObjectFromString` function accepts a `variable` property. You can use this property to filter retrieved records.
-
-For example:
-
-```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
-const query = remoteQueryObjectFromString({
- entryPoint: "my_custom",
- fields: ["name", "id"],
- variables: {
- filters: {
- id: "mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
- },
- },
-})
-
-const result = await remoteQuery(query)
-```
-
-The `variable` property’s value is an object having the property `filters`, whose value is also an object. The object’s properties are the field names, and the values are the filters to apply.
-
-You can also filter by multiple values using array values. For example:
-
```ts highlights={[["6"], ["7"], ["8"], ["9"]]}
const query = remoteQueryObjectFromString({
- entryPoint: "my_custom",
- fields: ["name", "id"],
+ entryPoint: "custom_product_data",
+ fields: ["id", "custom_field", "product.title"],
variables: {
filters: {
id: [
- "mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
- "mc_01HWSVWK3KYHKQEE6QGS2JC3FX",
+ "cpd_01HWSVWR4D2XVPQ06DQ8X9K7AX",
+ "cpd_01HWSVWK3KYHKQEE6QGS2JC3FX",
],
},
},
@@ -128,52 +90,63 @@ const query = remoteQueryObjectFromString({
const result = await remoteQuery(query)
```
+The `remoteQueryObjectFromString` function accepts a `variables` property. You can use this property to filter retrieved records.
+
+
+
---
## Sort Records
-To sort returned records, pass an `order` property to the `variables` property's value. The `order` property is an object whose keys are field names, and values are either:
-
-- `ASC` to sort records by that field in ascending order.
-- `DESC` to sort records by that field in descending order.
-
-For example:
-
```ts highlights={[["4"], ["5"], ["6"]]}
const query = remoteQueryObjectFromString({
- entryPoint: "my_custom",
+ entryPoint: "custom_product_data",
+ fields: ["id", "custom_field", "product.title"],
variables: {
order: {
name: "DESC",
},
},
- fields: ["name", "id"],
})
const result = await remoteQuery(query)
```
-This retrieves the `MyCustom` records sorted by their name in descending order.
+To sort returned records, pass an `order` property to the `variables` property's value.
+
+The `order` property is an object whose keys are field names, and values are either:
+
+- `ASC` to sort records by that field in ascending order.
+- `DESC` to sort records by that field in descending order.
---
## Apply Pagination
-To paginate the returned records, pass the following properties to the `variables` property's value:
-
-- `skip`: (required to apply pagination) The number of records to skip before fetching the results.
-- `take`: The number of records to fetch.
-
-For example:
-
-```ts highlights={[["4", "", "The number of records to skip before fetching the results."], ["5", "", "The number of records to fetch."]]}
+```ts highlights={[["5", "skip", "The number of records to skip before fetching the results."], ["6", "take", "The number of records to fetch."]]}
const query = remoteQueryObjectFromString({
- entryPoint: "my_custom",
+ entryPoint: "custom_product_data",
+ fields: ["id", "custom_field", "product.title"],
variables: {
skip: 0,
take: 10,
},
- fields: ["name", "id"],
})
const {
@@ -182,7 +155,10 @@ const {
} = await remoteQuery(query)
```
-This skips no records and returns the first `10` records.
+To paginate the returned records, pass the following properties to the `variables` property's value:
+
+- `skip`: (required to apply pagination) The number of records to skip before fetching the results.
+- `take`: The number of records to fetch.
When the pagination fields are provided, the `remoteQuery` returns an object having two properties:
@@ -222,67 +198,73 @@ When the pagination fields are provided, the `remoteQuery` returns an object hav
The remote query function alternatively accepts a string with GraphQL syntax as the query.
-For example:
-
-```ts title="src/api/store/query/route.ts" apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/query"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/medusa"
-import { remoteQueryObjectFromString } from "@medusajs/utils"
-import { ContainerRegistrationKeys } from "@medusajs/utils"
-import type {
- RemoteQueryFunction,
-} from "@medusajs/modules-sdk"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const remoteQuery: RemoteQueryFunction = req.scope.resolve(
- ContainerRegistrationKeys.REMOTE_QUERY
- )
-
- const query = `
- query {
- my_custom {
- id
- name
- }
- }
- `
-
- res.json({
- my_customs: await remoteQuery(query),
- })
-}
-```
-
-This runs a GraphQL query to retrieve `MyCustom` records.
-
-### Example Usages
-
-
+
+ Basic Usage
Apply Filters
Sort Records
Apply Pagination
+
+
+ ### Basic GraphQL usage
+
+ ```ts title="src/api/store/query/route.ts" apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/query"
+ import {
+ MedusaRequest,
+ MedusaResponse,
+ } from "@medusajs/medusa"
+ import { remoteQueryObjectFromString } from "@medusajs/utils"
+ import { ContainerRegistrationKeys } from "@medusajs/utils"
+ import type {
+ RemoteQueryFunction,
+ } from "@medusajs/modules-sdk"
+
+ export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+ ): Promise {
+ const remoteQuery: RemoteQueryFunction = req.scope.resolve(
+ ContainerRegistrationKeys.REMOTE_QUERY
+ )
+
+ const query = `
+ query {
+ custom_product_data {
+ id
+ custom_field
+ product {
+ title
+ }
+ }
+ }
+ `
+
+ const result = await remoteQuery(query)
+
+ res.json({
+ custom_product_data: result,
+ })
+ }
+ ```
+
+
### Apply Filters with GraphQL
The `remoteQuery` function accepts as a second parameter an object of variables to reference in the GraphQL query.
- For example, to filter the items by their ID:
-
- ```ts highlights={[["2"], ["3"], ["12"], ["13"], ["14"]]}
+ ```ts highlights={[["2"], ["3"], ["16"], ["17"], ["18"], ["19"]]}
const query = `
query($id: ID) {
- my_custom(id: $id) {
+ custom_product_data(id: $id) {
id
- name
+ custom_field
+ product {
+ title
+ }
}
}
`
@@ -290,19 +272,20 @@ This runs a GraphQL query to retrieve `MyCustom` records.
const result = await remoteQuery(
query,
{
- id: "mc_01HWSVWK3KYHKQEE6QGS2JC3FX"
+ id: [
+ "cpd_01HWSVWR4D2XVPQ06DQ8X9K7AX",
+ "cpd_01HWSVWK3KYHKQEE6QGS2JC3FX",
+ ]
}
)
```
- The variable’s value can also be an array to match multiple items.
-
### Sort Records with GraphQL
- To sort the records by a field, pass an `order` argument whose value is an object. The object’s key is the field’s name, and its value is either:
+ To sort the records by a field, pass in the query an `order` argument whose value is an object. The object’s key is the field’s name, and the value is either:
- `ASC` to sort items by that field in ascending order.
- `DESC` to sort items by that field in descending order.
@@ -312,9 +295,12 @@ This runs a GraphQL query to retrieve `MyCustom` records.
```ts highlights={[["3"]]}
const query = `
query {
- my_custom(order: {name: DESC}) {
+ custom_product_data(order: {custom_field: DESC}) {
id
- name
+ custom_field
+ product {
+ title
+ }
}
}
`
@@ -334,9 +320,12 @@ This runs a GraphQL query to retrieve `MyCustom` records.
```ts highlights={[["2"], ["3"]]}
const query = `
query($skip: Int, $take: Int) {
- my_custom(skip: $skip, take: $take) {
+ custom_product_data(skip: $skip, take: $take) {
id
- name
+ custom_field
+ product {
+ title
+ }
}
}
`
diff --git a/www/apps/book/app/advanced-development/modules/service-factory/page.mdx b/www/apps/book/app/advanced-development/modules/service-factory/page.mdx
index 4e1f1743b7..8a8b294104 100644
--- a/www/apps/book/app/advanced-development/modules/service-factory/page.mdx
+++ b/www/apps/book/app/advanced-development/modules/service-factory/page.mdx
@@ -1,3 +1,5 @@
+import { Table } from "docs-ui"
+
export const metadata = {
title: `${pageNumber} Service Factory`,
}
@@ -8,9 +10,13 @@ In this document, you’ll learn about what the service factory is and how to us
## What is the Service Factory?
-Medusa provides a service factory that your module’s services can extend. The service factory implements data management methods for the data models you specify.
+Medusa provides a service factory that your module’s main service can extend. The service factory implements data management methods for your data models.
-If your service provides functionalities to manage the records of a data model, it’s encouraged to extend the service factory.
+
+
+- Your service provides data-management functionalities of your data models.
+
+
---
@@ -25,9 +31,9 @@ Your module must be using the following loaders from the previous chapters:
-The `ModulesSdkUtils` imported from `@medusajs/utils` has a utility function `abstractModuleServiceFactory` that creates a service instance with basic data management methods.
+Medusa provides the service factory as a function your service extends. The function creates and returns a service class with generated data-management methods.
-So, to create a service that extends the service instance returned by the factory, call `abstractModuleServiceFactory` and extend its returned value:
+For example, create the file `src/modules/hello/service.ts` with the following content:
```ts title="src/modules/hello/service.ts"
import { ModulesSdkUtils } from "@medusajs/utils"
@@ -43,40 +49,117 @@ class HelloModuleService extends ModulesSdkUtils
export default HelloModuleService
```
-### Function Parameters
+### abstractModuleServiceFactory Parameters
The `abstractModuleServiceFactory` function accepts two parameters:
-1. The first parameter is the main data model this service is creating methods for. It's the `MyCustom` in the example above.
-2. The second parameter is an array of data models to generate methods for. If you have the `AnotherCustom` data model, this is where you add it.
+1. The first parameter is the main data model this service is creating methods for. For example, `MyCustom`.
+2. The second parameter is an array of data models to generate methods for. If you have an `AnotherCustom` data model, this is where you add it.
### Generated Methods
The service factory generates the following methods for the main data model:
-- `list`: Returns an array of records based on filters and pagination configurations.
-- `listAndCount`: Returns a tuple of an array of records and the total count of available records based on the filters and pagination configurations provided.
-- `retrieve`: Returns a record by its ID.
-- `delete`: Deletes records by an ID or filter.
-- `softDelete`: Soft-deletes a record by an ID or filter. This only applies if the data model has a `deleted_at` field.
-- `restore`: Restores a soft-deleted record by an ID or filter. This only applies if the data model has a `deleted_at` field.
+
+
+
+ Method
+ Description
+
+
+
+
+
+
+ `list`
-For other provided data models, the same methods are generated, with the data model’s name appended to the end of the method. For example, `listAnotherCustom` or `retrieveAnotherCustom`.
+
+
+
+ Retrieves an array of records based on filters and pagination configurations.
+
+
+
+
+
+
+ `listAndCount`
+
+
+
+
+ Retrieves a tuple of an array of records and the total count of available records based on the filters and pagination configurations provided.
+
+
+
+
+
+
+ `retrieve`
+
+
+
+
+ Retrieves a record by its ID.
+
+
+
+
+
+
+ `delete`
+
+
+
+
+ Deletes records by an ID or filter.
+
+
+
+
+
+
+ `softDelete`
+
+
+
+
+ Soft-deletes a record by an ID or filter. This only applies if the data model has a `deleted_at` field.
+
+
+
+
+
+
+ `restore`
+
+
+
+
+ Restores a soft-deleted record by an ID or filter. This only applies if the data model has a `deleted_at` field.
+
+
+
+
+
+
+The same methods are generated for data models passed in the second parameter of the service factory. The methods' names end with the data model's name. For example, `listAnotherCustom`.
### Type Arguments
For a better development experience and accurate typing of the generated methods, the `abstractModuleServiceFactory` function accepts three type arguments:
export const typeArgsHighlights = [
- ["24", "InjectedDependencies", "The type of dependencies resolved from the Module's container."],
- ["25", "MyCustomDTO", "The expected input/output type of the main data model's generated methods."],
- ["26", "AllModelsDTO", "The expected input/output type of the generated methods of every data model."],
+ ["25", "InjectedDependencies", "The type of dependencies resolved from the Module's container."],
+ ["26", "MyCustomDTO", "The expected input/output type of the main data model's generated methods."],
+ ["27", "AllModelsDTO", "The expected input/output type of the generated methods of every data model."],
]
```ts title="src/modules/hello/service.ts" highlights={typeArgsHighlights}
import { ModulesSdkUtils } from "@medusajs/utils"
import { MyCustom } from "./models/my-custom"
+// recommended to define type in another file
type MyCustomDTO = {
id: string
name: string
@@ -107,54 +190,6 @@ class HelloModuleService extends ModulesSdkUtils
export default HelloModuleService
```
-1. The first one is the type of the injected dependencies received as the first parameter of the service’s constructor. You use this type to specify the resources to resolve from the module’s container.
-2. The second one is the expected input and output type of the main data model’s methods (for example, the `list` method). This is useful to infer the correct input/output of the generated methods of the main model.
-3. The third type is the expected input and output type of all data models that the service factory generates methods for (for example, the `listAnotherCustom` method). This is useful to infer the generated methods for each specified data model, with the correct input/output for each of them.
-
----
-
-## Test it Out
-
-To test out the service, create an API route at `src/api/store/custom/route.ts` with the following content:
-
-```ts title="src/api/store/custom/route.ts"
-import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
-import HelloModuleService from "../../../modules/hello/service"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const helloModuleService: HelloModuleService =
- req.scope.resolve(
- "helloModuleService"
- )
-
- res.json({
- my_customs: await helloModuleService.list(),
- })
-}
-
-```
-
-Then, start your Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Finally, send a request `GET` request to `/store/custom`:
-
-```bash apiTesting testApiUrl="http://localhost:9000/store/custom" testApiMethod="GET"
-curl http://localhost:9000/store/custom
-```
-
-You’ll receive the following response:
-
-```json
-{
- "my_customs": []
-}
-```
-
-In the next chapter, you’ll learn how to add create and update methods to the service generated by the service factory.
+1. The first one is the type of the dependencies to resolve from the module's container.
+2. The second one is the expected input and output type of the main data model’s methods.
+3. The third type is the expected input and output type of all data models that the service factory generates methods for.
diff --git a/www/apps/book/app/advanced-development/page.mdx b/www/apps/book/app/advanced-development/page.mdx
index cce4ffcd92..cd3890d946 100644
--- a/www/apps/book/app/advanced-development/page.mdx
+++ b/www/apps/book/app/advanced-development/page.mdx
@@ -11,6 +11,8 @@ The next chapters dive deeper into each concept. By the end of these chapters, y
- Expose API routes with control over authentication, parsing request bodies, and more.
- Create data models with complex fields and relations.
- Manage data models in services.
-- Link data models in your module to data models of other modules.
-- Emit and handle custom events.
-- Create advanced workflows with steps across services and third-party systems.
+- Create relationships between modules.
+- Create loaders outside of modules.
+- Access events payloads.
+- Create advanced workflows and configure retries and timeout.
+- Add new pages to the Medusa Admin.
diff --git a/www/apps/book/app/advanced-development/workflows/advanced-example/page.mdx b/www/apps/book/app/advanced-development/workflows/advanced-example/page.mdx
index 971fd0f24b..2040cdc2bb 100644
--- a/www/apps/book/app/advanced-development/workflows/advanced-example/page.mdx
+++ b/www/apps/book/app/advanced-development/workflows/advanced-example/page.mdx
@@ -43,7 +43,7 @@ export const updateProductHighlights = [
["13", "resolve", "Resolve the `ProductService` from the Medusa container."],
["16", "previousProductData", "Retrieve the `previousProductData` to pass it to the compensation function."],
["19", "", "Update the product."],
- ["33", "", "Revert the product’s data using the `previousProductData` passed from the step to the compensation function."]
+ ["39", "", "Revert the product’s data using the `previousProductData` passed from the step to the compensation function."]
]
```ts title="src/workflows/update-product-erp/steps/update-product.ts" highlights={updateProductHighlights}
@@ -65,7 +65,7 @@ const updateProduct = createStep(
const previousProductData =
await productModuleService.retrieve(id)
- const product = await productModuleService.update([input])
+ const product = await productModuleService.update(id, input)
return new StepResponse(product, {
// pass to compensation function
@@ -77,15 +77,37 @@ const updateProduct = createStep(
const productModuleService: IProductModuleService =
context.container.resolve(ModuleRegistrationName.PRODUCT)
- const { type, ...previousData } = previousProductData
+ const {
+ id,
+ type,
+ options,
+ variants,
+ ...previousData
+ } = previousProductData
- await productModuleService.update([
+ await productModuleService.update(
+ id,
{
...previousData,
- },
- ])
+ variants: variants.map((variant) => {
+ const variantOptions = {}
- await productModuleService.updateTypes(type)
+ variant.options.forEach((option) => {
+ variantOptions[option.option.title] = option.value
+ })
+
+ return {
+ ...variant,
+ options: variantOptions
+ }
+ }),
+ options: options.map((option) => ({
+ ...option,
+ values: option.values.map((value) => value.value)
+ })),
+ type_id: type.id
+ }
+ )
}
)
@@ -102,25 +124,25 @@ You also pass a compensation function as a second parameter to `createStep`. The
In the compensation function, you revert the product’s data using the `previousProductData` passed from the step to the compensation function.
-
-
-The compensation functions of each step ensure data consistency across Medusa and external services and systems when errors occur.
-
-
-
---
## 3. Create Step 2: Update ERP
-The second step in the workflow receives the same input. It updates the product’s details in the ERP system using the `ErpService`.
+The second step in the workflow receives the same input. It updates the product’s details in the ERP system.
+
+
+
+The `ErpModuleService` used is assumed to be created in a module.
+
+
Create the file `src/workflows/update-product-erp/steps/update-erp.ts` with the following content:
export const updateErpHighlights = [
- ["12", "resolve", "Resolve the `ErpService` from the Medusa container."],
+ ["12", "resolve", "Resolve the `erpModuleService` from the Medusa container."],
["17", "previousErpData", "Retrieve the `previousErpData` to pass it to the compensation function."],
["21", "updateProductErpData", "Update the product’s ERP data and return the data from the ERP system."],
- ["34", "updateProductErpData", "Revert the product's data in the ERP system to its previous state using the `previousErpData`."]
+ ["37", "updateProductErpData", "Revert the product's data in the ERP system to its previous state using the `previousErpData`."]
]
```ts title="src/workflows/update-product-erp/steps/update-erp.ts" highlights={updateErpHighlights}
@@ -129,22 +151,25 @@ import {
StepResponse,
} from "@medusajs/workflows-sdk"
import { UpdateProductAndErpWorkflowInput } from ".."
-import ErpService from "../../../services/erp"
+import ErpModuleService from "../../../modules/erp/service"
const updateErp = createStep(
"update-erp",
async (input: UpdateProductAndErpWorkflowInput, context) => {
- const erpService: ErpService =
- context.container.resolve("erpService")
+ const erpModuleService: ErpModuleService =
+ context.container.resolve("erpModuleService")
const { id, ...updatedData } = input
// get previous ERP data
const previousErpData =
- await erpService.retrieveProductErpDetails(id)
+ await erpModuleService.retrieveProductErpDetails(id)
const updatedErpData =
- await erpService.updateProductErpData(id, updatedData)
+ await erpModuleService.updateProductErpData(
+ id,
+ updatedData
+ )
return new StepResponse(updatedErpData, {
// pass to compensation function
@@ -154,7 +179,7 @@ const updateErp = createStep(
},
// compensation function
async ({ previousErpData, productId }, context) => {
- const erpService: ErpService =
+ const erpService: ErpModuleService =
context.container.resolve("erpService")
await erpService.updateProductErpData(
@@ -169,7 +194,7 @@ export default updateErp
In the step:
-- You resolve the `ErpService` from the Medusa container.
+- You resolve the `erpModuleService` from the Medusa container.
- You retrieve the `previousErpData` to pass it to the compensation function.
- You update the product’s ERP data and return the data from the ERP system.
@@ -228,8 +253,13 @@ import updateProductAndErpWorkflow, {
UpdateProductAndErpWorkflowInput,
} from "../../../../../workflows/update-product-erp"
+type ProductErpReq = Omit<
+ UpdateProductAndErpWorkflowInput,
+ "id"
+>
+
export const POST = async (
- req: MedusaRequest,
+ req: MedusaRequest,
res: MedusaResponse
) => {
// skipping validation for simplicity
diff --git a/www/apps/book/app/advanced-development/workflows/compensation-function/page.mdx b/www/apps/book/app/advanced-development/workflows/compensation-function/page.mdx
index 1be6785c81..220880152f 100644
--- a/www/apps/book/app/advanced-development/workflows/compensation-function/page.mdx
+++ b/www/apps/book/app/advanced-development/workflows/compensation-function/page.mdx
@@ -10,9 +10,7 @@ In this chapter, you'll learn how to add a compensation function to a step.
Errors can occur in a workflow. To avoid data inconsistency, define a function to run when an error occurs in a step. This function is called the compensation function.
-Each step can have a compensation function. The compensation function only runs if an error occurs throughout the Workflow. It’s useful to undo or roll back actions you’ve performed in a step.
-
-For example, change step one to add a compensation function and step two to throw an error:
+For example:
```ts title="src/workflows/hello-world.ts" highlights={[["16"], ["17"], ["18"]]}
// other imports...
@@ -34,7 +32,16 @@ const step1 = createStep(
console.log("Oops! Rolling back my changes...")
}
)
+```
+Each step can have a compensation function. The compensation function only runs if an error occurs throughout the Workflow. It’s useful to undo or roll back actions you’ve performed in a step.
+
+### Test Compensation Function
+
+1. Add another step that throws an error:
+
+```ts title="src/workflows/hello-world.ts"
+// ...
const step2 = createStep(
"step-2",
async () => {
@@ -43,9 +50,7 @@ const step2 = createStep(
)
```
-### Test Compensation Function
-
-1. Use the steps in a workflow. For example:
+2. Use the steps in a workflow. For example:
```ts title="src/workflows/hello-world.ts"
import {
@@ -74,7 +79,7 @@ const myWorkflow = createWorkflow<
export default myWorkflow
```
-2. Execute the workflow from a resource, such as an API route:
+3. Execute the workflow from a resource, such as an API route:
```ts title="src/api/store/workflow/route.ts"
import type {
@@ -94,13 +99,13 @@ export async function GET(
}
```
-3. Run the Medusa application:
+4. Run the Medusa application:
```bash npm2yarn
npm run dev
```
-4. Send a `GET` request to `/store/workflow`:
+5. Send a `GET` request to `/store/workflow`:
```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/workflows"
curl http://localhost:9000/store/workflow
diff --git a/www/apps/book/app/advanced-development/workflows/constructor-constraints/page.mdx b/www/apps/book/app/advanced-development/workflows/constructor-constraints/page.mdx
index 452182848d..ded9b7aa48 100644
--- a/www/apps/book/app/advanced-development/workflows/constructor-constraints/page.mdx
+++ b/www/apps/book/app/advanced-development/workflows/constructor-constraints/page.mdx
@@ -69,7 +69,7 @@ const myWorkflow = createWorkflow<
const str2 = step2(input)
return {
- message: `${input.str1}${input.str2}`,
+ message: `${str1}${str2}`,
}
})
diff --git a/www/apps/book/app/advanced-development/workflows/long-running-workflow/page.mdx b/www/apps/book/app/advanced-development/workflows/long-running-workflow/page.mdx
index 7476538301..9ab7e9fcc5 100644
--- a/www/apps/book/app/advanced-development/workflows/long-running-workflow/page.mdx
+++ b/www/apps/book/app/advanced-development/workflows/long-running-workflow/page.mdx
@@ -18,7 +18,7 @@ A long-running workflow is a workflow that continues its execution in the backgr
## Configure Long-Running Workflows
-A workflow is considered long-running if one or more of its steps have their `async` configuration set to `true`.
+A workflow is considered long-running if at least one step has its `async` configuration set to `true`.
For example, consider the following workflow and steps:
diff --git a/www/apps/book/app/advanced-development/workflows/workflow-timeout/page.mdx b/www/apps/book/app/advanced-development/workflows/workflow-timeout/page.mdx
index 1deede134d..a8e8729972 100644
--- a/www/apps/book/app/advanced-development/workflows/workflow-timeout/page.mdx
+++ b/www/apps/book/app/advanced-development/workflows/workflow-timeout/page.mdx
@@ -12,6 +12,12 @@ By default, a workflow doesn’t have a timeout. It continues execution until it
You can configure a workflow’s timeout to indicate how long the workflow can run. Once the specified time is passed and the workflow is still running, the workflow is considered failed and an error is thrown.
+
+
+Timeout doesn't stop the execution of a running step. The timeout only affects the status of the workflow and its result.
+
+
+
For example:
```ts title="src/workflows/hello-world.ts" highlights={[["22"]]}
@@ -63,6 +69,12 @@ A workflow’s timeout error is returned in the `errors` property of the workflo
Alternatively, you can configure timeout for a step rather than the entire workflow.
+
+
+As mentioned in the previous section, the timeout doesn't stop the execution of the step. It only affects the step's status and output.
+
+
+
For example:
```tsx
diff --git a/www/apps/book/app/architectural-concepts/cache-module/page.mdx b/www/apps/book/app/architectural-concepts/cache-module/page.mdx
index 52a90728d3..8c1edb21bc 100644
--- a/www/apps/book/app/architectural-concepts/cache-module/page.mdx
+++ b/www/apps/book/app/architectural-concepts/cache-module/page.mdx
@@ -12,12 +12,6 @@ A Cache Module is used to cache the results of computations such as price select
The underlying database, third-party service, or caching logic is flexible since it's implemented in a module. You can choose from Medusa’s cache modules or create your own to support something more suitable for your architecture.
-
-
-Refer to the Cache Modules reference for a list of Medusa’s Cache Modules.
-
-
-
---
## Default Cache Module
diff --git a/www/apps/book/app/basics/services/page.mdx b/www/apps/book/app/basics/_services/page.mdx
similarity index 100%
rename from www/apps/book/app/basics/services/page.mdx
rename to www/apps/book/app/basics/_services/page.mdx
diff --git a/www/apps/book/app/basics/admin-customizations/page.mdx b/www/apps/book/app/basics/admin-customizations/page.mdx
index 1f58d85ec0..5d95d0cab6 100644
--- a/www/apps/book/app/basics/admin-customizations/page.mdx
+++ b/www/apps/book/app/basics/admin-customizations/page.mdx
@@ -14,9 +14,11 @@ Admin customizations are coming soon.
## Overview
-The Medusa Admin is installed in your Medusa application and runs at port `7001` when you start the Medusa application.
+The Medusa Admin is an admin dashboard that merchants use to manage their store's data.
-You can extend or customize the Medusa Admin to add widgets or new pages that interact with API routes and provide admin users with custom functionalities.
+You can extend the Medusa Admin to add widgets and new pages. Your customizations interact with API routes to provide merchants with custom functionalities.
+
+The Medusa Admin is installed in your Medusa application and runs at port `7001` when you start the Medusa application.
---
diff --git a/www/apps/book/app/basics/api-routes/page.mdx b/www/apps/book/app/basics/api-routes/page.mdx
index 7267e7317e..9fe6a85b6d 100644
--- a/www/apps/book/app/basics/api-routes/page.mdx
+++ b/www/apps/book/app/basics/api-routes/page.mdx
@@ -18,7 +18,7 @@ The Medusa core application provides a set of admin and store API routes out-of-
An API Route is created in a TypeScript or JavaScript file under the `/src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.
-Each route file holds API Route handler functions for each HTTP method (`GET`, `POST`, `DELETE`, etc…).
+Each route file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
For example, to create a `GET` API Route at `/store/hello-world`, create the file `src/api/store/hello-world/route.ts` with the following content:
@@ -54,7 +54,7 @@ curl http://localhost:9000/store/hello-world
---
-## When to Use
+## When to Use API Routes
diff --git a/www/apps/book/app/basics/commerce-modules/page.mdx b/www/apps/book/app/basics/commerce-modules/page.mdx
index 84a9159029..9fd5fa1a9e 100644
--- a/www/apps/book/app/basics/commerce-modules/page.mdx
+++ b/www/apps/book/app/basics/commerce-modules/page.mdx
@@ -45,4 +45,8 @@ export const GET = async (
When you resolve the `ModuleRegistrationName.PRODUCT` (or `productModuleService`) registration name, you're actually resolving the main service of the Product Module.
+
+
To resolve the main service of any commerce module, use the `ModuleRegistrationName` enum imported from `@medusajs/modules-sdk` to refer to its registration name in the Medusa container.
+
+
\ No newline at end of file
diff --git a/www/apps/book/app/basics/data-models/page.mdx b/www/apps/book/app/basics/data-models/page.mdx
index c220bdc68f..3c2b6187db 100644
--- a/www/apps/book/app/basics/data-models/page.mdx
+++ b/www/apps/book/app/basics/data-models/page.mdx
@@ -25,7 +25,7 @@ Data models are based on [MikroORM](https://mikro-orm.io/docs/quick-start). So,
-A data model is a class created in a TypeScript or JavaScript file created in a module under its `models` directory.
+A data model is a class created in a TypeScript or JavaScript file under a module's `models` directory.
For example, create the file `src/modules/hello/models/my-custom.ts` with the following content:
@@ -73,7 +73,7 @@ The `generateEntityId` utility method prefixes the `id` of a record with the str
After creating the data model, you must create a migration that creates a table in your database for this data model.
-A migration is a class that implements an `up` and `down` method, where the `up` method reflects changes on the database, and the `down` method reverts the changes from the database.
+A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It implements an `up` and `down` method, where the `up` method reflects changes on the database, and the `down` method reverts the changes from the database.
MikroORM provides a CLI tool that helps you generate migrations. To use it:
@@ -106,7 +106,7 @@ A migration is a class that implements an `up` and `down` method, where the `up`
- You can add this command as a script in `package.json` for easy usage in the future. Use this command whenever you want to generate a new migration in your module.
+ Add this command as a script in `package.json` for easy usage in the future. Use this command whenever you want to generate a new migration in your module.
@@ -116,7 +116,7 @@ A migration is a class that implements an `up` and `down` method, where the `up`
For example:
-```ts
+```ts title="src/modules/migrations/Migration20240429090012.ts"
import { Migration } from "@mikro-orm/migrations"
export class Migration20240429090012 extends Migration {
@@ -142,7 +142,7 @@ The queries performed in each of the methods use PostgreSQL syntax.
### Add Migration to Module Definition
-After creating the migration, you must add it to your module's definition. Otherwise, the Medusa application won't run it.
+After creating the migration, you must add it to your module's definition.
To add a module's migrations to its definitions, use the `ModulesSdkUtils` utility functions imported from `@medusajs/utils`. It has functions to create and define the migration scripts in your module definition.
@@ -213,7 +213,7 @@ If ran successfully, the `my_custom` table will be created in the database.
---
-## When to Use
+## When to Use Data Models
diff --git a/www/apps/book/app/basics/events-and-subscribers/page.mdx b/www/apps/book/app/basics/events-and-subscribers/page.mdx
index 2a412a8ed6..c90d88ee5d 100644
--- a/www/apps/book/app/basics/events-and-subscribers/page.mdx
+++ b/www/apps/book/app/basics/events-and-subscribers/page.mdx
@@ -39,12 +39,14 @@ export const config: SubscriberConfig = {
A subscriber file must export:
-- The subscriber function which is an asynchronous function executed whenever the associated event is triggered.
+- The subscriber function that is an asynchronous function executed whenever the associated event is triggered.
- A configuration object defining the event this subscriber is listening to.
The above subscriber listens to the `ProductService.Events.CREATED` (`product-created`) event. Whenever the event is emitted, it logs in the terminal `A product is created`.
-### Test Subscriber
+{/* TODO add when we have the admin dashboard to use with V2 for easy testing. */}
+
+{/* ### Test Subscriber
To test the subscriber, start the Medusa application:
@@ -59,11 +61,11 @@ info: Processing product.created which has 1 subscribers
A product is created
```
-The first message indicates that the `product.created` event was emitted, and the second one is the message logged from the subscriber.
+The first message indicates that the `product.created` event was emitted, and the second one is the message logged from the subscriber. */}
---
-## When to Use
+## When to Use Subscribers
diff --git a/www/apps/book/app/basics/loaders/page.mdx b/www/apps/book/app/basics/loaders/page.mdx
index 25d11e62ee..495133242a 100644
--- a/www/apps/book/app/basics/loaders/page.mdx
+++ b/www/apps/book/app/basics/loaders/page.mdx
@@ -10,19 +10,13 @@ In this chapter, you’ll learn about loaders and how to use them.
A loader is a function executed when the Medusa application starts.
-A loader can be created either within a module or in the `src/loaders` directory.
-
-
-
-This guide explains the first approach. This second approach will be explained in later chapters.
-
-
+A module can define loaders to execute on application start-up.
---
## How to Create a Loader?
-A loader is created in a TypeScript or JavaScript file under the `loaders` directory of a module. The file must export the loader function.
+A loader is created in a TypeScript or JavaScript file under a module's `loaders` directory.
For example, create the file `src/modules/hello/loaders/hello-world.ts` with the following content:
@@ -36,7 +30,7 @@ export default function helloWorldLoader() {
### Export Loader in Module Definition
-You must export your loaders in the module definition. Otherwise, your Medusa application won't run them.
+You must export your loaders in the module definition.
To do that, import the loader in `src/modules/hello/index.ts` and export it in the module's definition:
@@ -52,7 +46,7 @@ export default {
}
```
-The value of the `loaders` property of the module definition is an array of loader functions.
+The value of the `loaders` property is an array of loader functions.
### Test Loader
@@ -70,13 +64,12 @@ Among the messages logged in the terminal, you’ll see the following message:
---
-## When to Use
+## When to Use Loaders
- You're performing an action at application start-up.
- You're establishing a one-time connection with an external system.
-- You're registering a custom resource in the container.
diff --git a/www/apps/book/app/basics/medusa-container/page.mdx b/www/apps/book/app/basics/medusa-container/page.mdx
index cb669ae9a5..0f1bae432c 100644
--- a/www/apps/book/app/basics/medusa-container/page.mdx
+++ b/www/apps/book/app/basics/medusa-container/page.mdx
@@ -8,7 +8,9 @@ In this chapter, you’ll learn about Medusa’s Medusa container and how to use
## What is the Medusa container?
-The Medusa container holds all registered resources in the Medusa application. You use it to resolve resources, such as services.
+The Medusa container holds all resources registered in the Medusa application. You have access to it in your customizations.
+
+You use the Medusa container to resolve resources, such as services.
For example, in a custom API route you can resolve any service registered in the Medusa application using the `scope.resolve` method of the `MedusaRequest` parameter:
@@ -40,4 +42,4 @@ export const GET = async (
}
```
-This route handler function resolves the `IProductModuleService` and uses it to return the full count of products in the Medusa application.
+You resolve the `IProductModuleService` and uses it to return the full count of products in the Medusa application.
diff --git a/www/apps/book/app/basics/modules-and-services/page.mdx b/www/apps/book/app/basics/modules-and-services/page.mdx
index 3b1231b96a..a7bd6548ae 100644
--- a/www/apps/book/app/basics/modules-and-services/page.mdx
+++ b/www/apps/book/app/basics/modules-and-services/page.mdx
@@ -8,9 +8,9 @@ In this chapter, you’ll learn about modules, their main service, and how to cr
## What is a Module?
-A module is a package of reusable functionalities that can be integrated into your Medusa application without affecting the overall system.
+A module is a package of reusable functionalities. It can be integrated into your Medusa application without affecting the overall system.
-All commerce and architectural customizations and development start with creating a module.
+Use modules to customize or develop commerce and architectural features in your Medusa application.
---
@@ -24,15 +24,15 @@ All commerce and architectural customizations and development start with creatin
-Modules are created in a sub-directory of `src/modules`. The directory name is the camel-case name of the module.
+Modules are created in a sub-directory of `src/modules`.
For example, create the directory `src/modules/hello`.
### 1. Create a Service
-A module must defines a service. A service is a TypeScript or JavaScript class holding methods related to a business logic or commerce functionality.
+A module must define a service. A service is a TypeScript or JavaScript class holding methods related to a business logic or commerce functionality.
-For example, create the service `src/modules/hello/service.ts` with the following content:
+For example, create the file `src/modules/hello/service.ts` with the following content:
```ts title="src/modules/hello/service.ts"
export default class HelloModuleService {
@@ -42,8 +42,6 @@ export default class HelloModuleService {
}
```
-When the module is loaded in the Medusa application, its main service is registered in the Medusa container.
-
### 2. Export Module Definition
A module must have an `index.ts` file in its root directory that exports its definition. The definition specifies the main service of the module.
@@ -58,9 +56,9 @@ export default {
}
```
-### 3. Register Module in Configurations
+### 3. Add Module to Configurations
-The last step is to register the module in Medusa’s configurations.
+The last step is to add the module in Medusa’s configurations.
In `medusa-config.js`, add the module to the `modules` object:
@@ -73,11 +71,13 @@ const modules = {
}
```
-The key (`helloModuleService`) is the name of the module’s main service to be registered in the Medusa container. Its value is an object having the `resolve` property. `resolve` ’s value is either an `npm` package’s name or a path to the directory holding the module.
+Its key (`helloModuleService`) is the name of the module’s main service. It will be registered in the Medusa container with that name.
+
+Its value is an object having the `resolve` property, whose value is either a path to the directory holding the module or an `npm` package’s name.
-When `resolve` points to a directory, it must point to the transpiled module (the directory the module is in after running the `build` command). Hence, the path in the example above points to the module in the `dist` directory.
+When you run the `build` or `dev` command, your customizations are transpiled from the `src` directory into the `dist` directory. So, you point to the module in the `dist` directory.
@@ -129,10 +129,10 @@ You’ll receive the following response:
## When to Use Modules
-Use a module when you're implementing custom business logic or extending existing ones.
-
-Some common use cases of when a module is useful:
+
- You're implementing a custom commerce feature. For example, you're implementing digital products.
- You want to extend data models in other commerce modules, such as adding a field or a relation to the `Product` model.
- You want to re-use your custom commerce functionalities across Medusa applications or use them in other environments, such as Edge functions and Next.js apps.
+
+
diff --git a/www/apps/book/app/basics/modules-directory-structure/page.mdx b/www/apps/book/app/basics/modules-directory-structure/page.mdx
index de132d688d..8dfe5b9ec4 100644
--- a/www/apps/book/app/basics/modules-directory-structure/page.mdx
+++ b/www/apps/book/app/basics/modules-directory-structure/page.mdx
@@ -10,13 +10,13 @@ In this document, you'll learn about the expected files and directories in your
## index.ts
-The `index.ts` file in the root of your module's directory is the only required file. It must export the module's definition as explained in the [previous chapter](../modules-and-services/page.mdx).
+The `index.ts` file in the root of your module's directory is the only required file. It must export the module's definition as explained in a [previous chapter](../modules-and-services/page.mdx).
---
## service.ts
-A module must have a main service created in the `service.ts` file at the root of your module directory as explained in the [previous chapter](../modules-and-services/page.mdx).
+A module must have a main service. It's created in the `service.ts` file at the root of your module directory as explained in a [previous chapter](../modules-and-services/page.mdx).
---
diff --git a/www/apps/book/app/basics/page.mdx b/www/apps/book/app/basics/page.mdx
index 5cf77d590a..a4ae1c7720 100644
--- a/www/apps/book/app/basics/page.mdx
+++ b/www/apps/book/app/basics/page.mdx
@@ -11,10 +11,8 @@ By the end of these chapter, you’ll be able to:
- Expose your custom functionalities through REST APIs.
- Create custom modules that define custom business logic.
- Create custom data models.
-- Perform asynchronus actions when an event occurs.
- Execute scripts when the Medusa application starts.
-- Create scheduled jobs that run at a specified pattern during the Medusa application's runtime.
-- Create flows as a series of steps involving multiple services.
+- Perform asynchronus actions when an event occurs.
+- Run tasks at a specified time or pattern during the Medusa application's runtime.
+- Create custom flows as a series of steps involving multiple services.
- Customize the admin dashboard to inject components on existing pages or add new pages.
-
-You'll then use this knowledge to learn more about Medusa's modular architecture, and how to make customizations to core models and features.
diff --git a/www/apps/book/app/basics/scheduled-jobs/page.mdx b/www/apps/book/app/basics/scheduled-jobs/page.mdx
index e100ca262c..1523f4a847 100644
--- a/www/apps/book/app/basics/scheduled-jobs/page.mdx
+++ b/www/apps/book/app/basics/scheduled-jobs/page.mdx
@@ -6,9 +6,15 @@ export const metadata = {
In this chapter, you’ll learn about scheduled jobs and how to use them.
+
+
+Scheduled jobs are coming soon.
+
+
+
## What is a Scheduled Job?
-A scheduled job is a function that is executed at a specified interval of time in the background of your Medusa application. It’s like a cron job that runs during the application's runtime.
+A scheduled job is a function executed at a specified interval of time in the background of your Medusa application. It’s like a cron job that runs during the application's runtime.
For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day using a scheduled job.
@@ -44,7 +50,7 @@ export const config: ScheduledJobConfig = {
A scheduled job file must export:
-- The function executed whenever it’s time to run the scheduled job.
+- The function to be executed whenever it’s time to run the scheduled job.
- A configuration object defining the job. It has two properties:
- `name`: a unique name for the job.
- `schedule`: a [cron expression](https://crontab.guru/).
@@ -82,7 +88,7 @@ The first line indicates that the application is executing the scheduled job. Th
---
-## When to Use
+## When to Use Scheduled Jobs
@@ -93,7 +99,7 @@ The first line indicates that the application is executing the scheduled job. Th
-- You want the action to execute at a specified time interval while the Medusa application isn't running. Instead, use the operating system's equivalent of a cron job.
+- You want the action to execute at a specified time interval while the Medusa application **isn't** running. Instead, use the operating system's equivalent of a cron job.
- You want to execute the action once. Use loaders instead.
- You want to execute the action if an event occurs. Use subscribers instead.
diff --git a/www/apps/book/app/basics/workflows/page.mdx b/www/apps/book/app/basics/workflows/page.mdx
index 2baa3c78cd..de6d3a39ee 100644
--- a/www/apps/book/app/basics/workflows/page.mdx
+++ b/www/apps/book/app/basics/workflows/page.mdx
@@ -12,7 +12,7 @@ In this chapter, you’ll learn about workflows and how to define and execute th
A workflow is a series of queries and actions that complete a task.
-You construct a Workflow similar to how you create a JavaScript function, but unlike regular functions, a Medusa Workflow creates an internal representation of your steps. This makes it possible to keep track of your Workflow’s progress, automatically retry failing steps, and, if necessary, roll back steps.
+You construct a workflow similar to how you create a JavaScript function, but unlike regular functions, a workflow creates an internal representation of your steps. This makes it possible to keep track of your Workflow’s progress, automatically retry failing steps, and roll back steps.
---
@@ -20,7 +20,7 @@ You construct a Workflow similar to how you create a JavaScript function, but un
### 1. Create the Steps
-A workflow is made of a series of steps. A step is created using the `createStep` utility function.
+A workflow is made of a series of steps. A step is created using the `createStep` utility function imported from `@medusajs/workflows-sdk`.
Create the file `src/workflows/hello-world.ts` with the following content:
@@ -89,13 +89,13 @@ This creates a `hello-world` workflow. When you create a workflow, it’s constr
### 3. Execute the Workflow
-You can execute a workflow from different places within Medusa.
+You can execute a workflow from different resources within Medusa.
-- Use API Routes to execute the workflow in response to an API request or a webhook.
-- Use Subscribers to execute a workflow when an event is triggered.
-- Use Scheduled Jobs to execute a workflow on a regular schedule.
+- Use API routes to execute the workflow in response to an API request or a webhook.
+- Use subscribers to execute a workflow when an event is triggered.
+- Use scheduled jobs to execute a workflow on a regular schedule.
-To execute the workflow, invoke it passing the Medusa container as a parameter, then use its run method:
+To execute the workflow, invoke it passing the Medusa container as a parameter. Then, use its `run` method:
@@ -195,7 +195,7 @@ npm run dev
Then, send a `GET` request to `/store/workflow`:
-```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/workflows"
+```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/workflow"
curl http://localhost:9000/store/workflow
```
@@ -209,7 +209,7 @@ You’ll receive the following response:
---
-## When to Use
+## When to Use Workflows
@@ -223,7 +223,7 @@ You’ll receive the following response:
## Resolve Resources
-Each step in the workflow receives as a second parameter a `context` object. The object holds a `container` property which is the Medusa container. You can use it to resolve other resources, such as services, of your Medusa application.
+Each step in the workflow receives as a second parameter a `context` object. The object holds a `container` property which is the Medusa container. Use it to resolve other resources, such as services, of your Medusa application.
For example:
diff --git a/www/apps/book/app/cheatsheet/page.mdx b/www/apps/book/app/cheatsheet/page.mdx
index d185863ba8..9da723135a 100644
--- a/www/apps/book/app/cheatsheet/page.mdx
+++ b/www/apps/book/app/cheatsheet/page.mdx
@@ -34,10 +34,9 @@ This chapter provides a cheat sheet for Medusa's resources on when to use or not
Modules
- - You're implementing custom commerce functionalities to be reused across Medusa applications.
- - You're implementing custom architectural business logic.
- - You're adding extending another module's data model to add fields to it, such as the `Product` model.
- - You're completely replacing an existing commerce module to change how a certain functionality is implemented in the Medusa application.
+ - You're implementing a custom commerce feature. For example, you're implementing digital products.
+ - You want to extend data models in other commerce modules, such as adding a field or a relation to the Product model.
+ - You want to re-use your custom commerce functionalities across Medusa applications or use them in other environments, such as Edge functions and Next.js apps.
@@ -65,7 +64,6 @@ This chapter provides a cheat sheet for Medusa's resources on when to use or not
- You're performing an action at application start-up.
- You're establishing a one-time connection with an external system.
- - You're registering a custom resource in the container.
@@ -97,7 +95,7 @@ This chapter provides a cheat sheet for Medusa's resources on when to use or not
- - You want the action to execute at a specified time interval while the Medusa application isn't running. Instead, use the operating system's equivalent of a cron job.
+ - You want the action to execute at a specified time interval while the Medusa application **isn't** running. Instead, use the operating system's equivalent of a cron job.
- You want to execute the action once. Use loaders instead.
- You want to execute the action if an event occurs. Use subscribers instead.
@@ -122,50 +120,14 @@ This chapter provides a cheat sheet for Medusa's resources on when to use or not
Middlewares
- - You want to guard API routes by a custom condition.
+ - You want to protect API routes by a custom condition.
- You're modifying the request body.
- - You're registering custom resources in the Medusa container.
\-
-
-
-
- Batch Jobs
-
-
- - You’re implementing an asynchronous job that’s triggered manually.
- - You're tracking the status of the asynchronous job.
- - You're keeping track of all batch job executions, which are stored in the database.
-
-
-
-
- - You want to trigger the asynchronous job automatically. Instead, use scheduled jobs. You can also create the batch job in a scheduled job.
- - You want the task to be performed and finished before consecutive tasks. Instead, use a service.
-
-
-
-
- Plugins
-
-
- - You're integrating a third-party service into your Medusa application.
- - You're creating reusable Medusa application customizations.
- - You're providing features that are specific to a Medusa application, such as a migration tool from another platform to Medusa.
-
-
-
-
- - You want to run the plugin in isolation.
- - You want to link custom data models to those in Medusa's commerce modules.
- - You want to make architectural changes.
-
- For all of the cases above, use a Module.
-
diff --git a/www/apps/book/app/deployment/page.mdx b/www/apps/book/app/deployment/page.mdx
index e3114ee1a9..87ba7c84a6 100644
--- a/www/apps/book/app/deployment/page.mdx
+++ b/www/apps/book/app/deployment/page.mdx
@@ -41,6 +41,12 @@ Refer to [this reference](!resources!/deployment) to find how-to deployment guid
## Deploying the Medusa Admin
+
+
+Admin deployment details are still in the work.
+
+
+
### Deploy Admin with the Server
Since the Medusa Admin is a plugin installed in the server, you may choose to host them together.
diff --git a/www/apps/book/app/first-customizations/page.mdx b/www/apps/book/app/first-customizations/page.mdx
index e792775e7d..2cab68f589 100644
--- a/www/apps/book/app/first-customizations/page.mdx
+++ b/www/apps/book/app/first-customizations/page.mdx
@@ -60,4 +60,4 @@ In the response, you’ll receive the following object:
Congratulations, you’ve made your first customization with Medusa!
-You can now start your in-depth learning journey to become a Medusa expert.
+You can now start your in-depth learning journey to become an expert.
diff --git a/www/apps/book/app/more-resources/page.mdx b/www/apps/book/app/more-resources/page.mdx
new file mode 100644
index 0000000000..f5beefc356
--- /dev/null
+++ b/www/apps/book/app/more-resources/page.mdx
@@ -0,0 +1,13 @@
+export const metadata = {
+ title: `${pageNumber} More Resources`,
+}
+
+# {metadata.title}
+
+In this chapter, you’ll find more resources to aid you during your development with Medusa.
+
+## Medusa Resources Documentation
+
+The Medusa Resources documentation provides guides and references that are useful for your development. This book included links to parts of the Medusa Resources documentation where necessary.
+
+Check out the Medusa Resources documentation [here](!resources!).
\ No newline at end of file
diff --git a/www/apps/book/app/page.mdx b/www/apps/book/app/page.mdx
index fb90ffc82a..3bcf73ee94 100644
--- a/www/apps/book/app/page.mdx
+++ b/www/apps/book/app/page.mdx
@@ -1,4 +1,4 @@
-import { CheckCircleSolid, BuildingStorefront, BuildingsSolid, ComputerDesktop } from "@medusajs/icons"
+import { CheckCircleSolid, BuildingStorefront, BuildingsSolid, ComputerDesktop, FlyingBox } from "@medusajs/icons"
import { config } from "@/config"
export const metadata = {
@@ -32,11 +32,9 @@ With these tools, you save time you would spend with other platforms on maintain
Create your first Medusa store by running the following command:
```bash
-npx create-medusa-app@latest
+npx create-medusa-app@latest --v2
```
-This takes you on a journey to set up your store, learn about Medusa, and build your first customizations.
-
---
## Who is Medusa for
@@ -52,37 +50,43 @@ Medusa is for ambitious businesses and developers that are limited by traditiona
title: "Use Case: D2C",
text: "How Matt Sleeps built a unique D2C experience with Medusa",
href: "https://medusajs.com/blog/matt-sleeps/",
- startIcon:
+ startIcon: ,
+ isExternal: true
},
{
title: "Use Case: OMS",
text: "How Makro Pro Built an OMS with Medusa",
href: "https://medusajs.com/blog/makro-pro/",
- startIcon:
+ startIcon: ,
+ isExternal: true
},
{
title: "Use Case: Marketplace",
text: "How Foraged built a custom marketplace with Medusa",
href: "https://medusajs.com/blog/foraged/",
- startIcon:
+ startIcon: ,
+ isExternal: true
},
{
title: "Use Case: POS",
text: "How Tekla built a global webshop and a POS system with Medusa",
href: "https://medusajs.com/blog/tekla-pos/",
- startIcon:
+ startIcon: ,
+ isExternal: true
},
{
title: "Use Case: B2B",
text: "How Visionary built B2B commerce with Medusa",
href: "https://medusajs.com/blog/visionary/",
- startIcon:
+ startIcon: ,
+ isExternal: true
},
{
title: "Use Case: Platform",
text: "How Catalog built a B2B platform for SMBs with Medusa",
href: "https://medusajs.com/blog/catalog/",
- startIcon:
+ startIcon: ,
+ isExternal: true
}
]} itemsPerRow={2} />
@@ -92,4 +96,4 @@ Medusa is for ambitious businesses and developers that are limited by traditiona
This book is for TypeScript or JavaScript developers looking to master Medusa and build their commerce applications. By following this book, you’ll learn about Medusa’s concept, from basic to advanced, with easy-to-follow examples to assist you along the way.
-By the end of this book, you’ll be a Medusa expert, leading teams using Medusa from the beginning till deployment.
\ No newline at end of file
+By the end of this book, you’ll be an expert Medusa developer, leading teams using Medusa from the beginning till deployment.
\ No newline at end of file
diff --git a/www/apps/book/app/storefront-development/nextjs-starter/page.mdx b/www/apps/book/app/storefront-development/nextjs-starter/page.mdx
index 9618f4415a..00677e2ce7 100644
--- a/www/apps/book/app/storefront-development/nextjs-starter/page.mdx
+++ b/www/apps/book/app/storefront-development/nextjs-starter/page.mdx
@@ -6,9 +6,9 @@ export const metadata = {
In this chapter, you’ll learn how to install and use the Next.js Starter storefront.
-
+
-Check out the demo [here](https://next.medusajs.com/).
+The Next.js starter is currently in development to fully support Medusa v2.
@@ -21,16 +21,17 @@ Check out the demo [here](https://next.medusajs.com/).
-1. Create a Next.js project using the [Next.js Starter’s repository URL](https://github.com/medusajs/nextjs-starter-medusa):
+1. Clone the `feat/v2` branch of the [Next.js Starter](https://github.com/medusajs/nextjs-starter-medusa):
```bash
-npx create-next-app -e https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
+git clone https://github.com/medusajs/nextjs-starter-medusa -b feat/v2 my-medusa-storefront
```
-2. Change to the `my-medusa-storefront` directory and rename the template environment variable:
+2. Change to the `my-medusa-storefront` directory, install the dependencies, and rename the template environment variable:
-```bash
+```bash npm2yarn
cd my-medusa-storefront
+npm install
mv .env.template .env.local
```
diff --git a/www/apps/book/app/storefront-development/page.mdx b/www/apps/book/app/storefront-development/page.mdx
index 647b5f6f16..8bc0ea00f3 100644
--- a/www/apps/book/app/storefront-development/page.mdx
+++ b/www/apps/book/app/storefront-development/page.mdx
@@ -8,6 +8,6 @@ In the next chapters, you’ll learn about building a storefront for your Medusa
## Storefronts in Medusa’s Architecture
-Storefronts are built separately from the Medusa application. They interact with the Medusa application through the Store API Routes.
+Storefronts are built separately from the Medusa application. They interact with the Medusa application through the Store API routes.
-You have full freedom in how you choose to build your storefront. You can start with our Next.js starter storefront or build a storefront from scratch using your preferred framework or tech stack. Both of these approaches are explained in the next chapters.
+You're free to choose how to build your storefront. You can start with our Next.js starter storefront or build a storefront from scratch using your preferred framework or tech stack. Both of these approaches are explained in the next chapters.
diff --git a/www/apps/book/app/storefront-development/tips/page.mdx b/www/apps/book/app/storefront-development/tips/page.mdx
index 7600ddad89..29d11dae45 100644
--- a/www/apps/book/app/storefront-development/tips/page.mdx
+++ b/www/apps/book/app/storefront-development/tips/page.mdx
@@ -52,13 +52,7 @@ Products, carts, and orders are associated with a sales channel. If you don’t
-### Create a Publishable API Key
-
-You can create a publishable API key using either the [Medusa Admin dashboard](https://docs.medusajs.com/user-guide/settings/publishable-api-keys) or the [Admin API Routes](https://docs.medusajs.com/api/admin#publishable-api-keys).
-
-### Use the Publishable API Key
-
-Refer to the [Store API Reference](https://docs.medusajs.com/api/store#publishable-api-key) to learn how to use the publishable API key in your requests.
+Publishable API keys are provided by the [API key commerce module](!resources!/commerce-modules/api-key).
---
diff --git a/www/apps/book/sidebar.mjs b/www/apps/book/sidebar.mjs
index e1ba58f36a..93fe638d4b 100644
--- a/www/apps/book/sidebar.mjs
+++ b/www/apps/book/sidebar.mjs
@@ -93,10 +93,6 @@ export const sidebar = sidebarAttachHrefCommonOptions(
path: "/advanced-development/api-routes/protected-routes",
title: "Protected Routes",
},
- {
- path: "/advanced-development/api-routes/request-body-parsers",
- title: "Request Body Parsers",
- },
],
},
{
@@ -118,29 +114,17 @@ export const sidebar = sidebarAttachHrefCommonOptions(
path: "/advanced-development/modules/database-operations-in-services",
title: "Database Operations",
},
- {
- path: "/advanced-development/modules/example-crud-module-service",
- title: "Example: CRUD Service",
- },
{
path: "/advanced-development/modules/options",
title: "Module Options",
},
- {
- path: "/advanced-development/modules/queryable-modules",
- title: "Queryable Modules",
- },
- {
- path: "/advanced-development/modules/remote-query",
- title: "Remote Query",
- },
{
path: "/advanced-development/modules/module-relationships",
title: "Module Relationships",
},
{
- path: "/advanced-development/modules/example-module-relationship",
- title: "Example: Relationship",
+ path: "/advanced-development/modules/remote-query",
+ title: "Remote Query",
},
{
path: "/advanced-development/modules/link-modules",
@@ -190,10 +174,6 @@ export const sidebar = sidebarAttachHrefCommonOptions(
path: "/advanced-development/workflows/compensation-function",
title: "Compensation Function",
},
- {
- path: "/advanced-development/workflows/advanced-example",
- title: "Example: Advanced Workflow",
- },
{
path: "/advanced-development/workflows/access-workflow-errors",
title: "Access Workflow Errors",
@@ -214,6 +194,10 @@ export const sidebar = sidebarAttachHrefCommonOptions(
path: "/advanced-development/workflows/long-running-workflow",
title: "Long-Running Workflow",
},
+ {
+ path: "/advanced-development/workflows/advanced-example",
+ title: "Example: Advanced Workflow",
+ },
],
},
{
@@ -310,6 +294,10 @@ export const sidebar = sidebarAttachHrefCommonOptions(
path: "/deployment",
title: "Deployment",
},
+ {
+ path: "/more-resources",
+ title: "More Resources",
+ },
{
path: "/cheatsheet",
title: "Cheat Sheet",
diff --git a/www/packages/docs-ui/src/components/Card/index.tsx b/www/packages/docs-ui/src/components/Card/index.tsx
index 42861813ab..f3325b77aa 100644
--- a/www/packages/docs-ui/src/components/Card/index.tsx
+++ b/www/packages/docs-ui/src/components/Card/index.tsx
@@ -13,6 +13,7 @@ export type CardProps = {
contentClassName?: string
children?: React.ReactNode
showLinkIcon?: boolean
+ isExternal?: boolean
}
export const Card = ({
@@ -25,6 +26,7 @@ export const Card = ({
contentClassName,
children,
showLinkIcon = true,
+ isExternal = false,
}: CardProps) => {
return (
>
)}
diff --git a/www/yarn.lock b/www/yarn.lock
index d10a48e215..7855ee7609 100644
--- a/www/yarn.lock
+++ b/www/yarn.lock
@@ -2306,6 +2306,39 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-dialog@npm:1.0.4":
+ version: 1.0.4
+ resolution: "@radix-ui/react-dialog@npm:1.0.4"
+ dependencies:
+ "@babel/runtime": ^7.13.10
+ "@radix-ui/primitive": 1.0.1
+ "@radix-ui/react-compose-refs": 1.0.1
+ "@radix-ui/react-context": 1.0.1
+ "@radix-ui/react-dismissable-layer": 1.0.4
+ "@radix-ui/react-focus-guards": 1.0.1
+ "@radix-ui/react-focus-scope": 1.0.3
+ "@radix-ui/react-id": 1.0.1
+ "@radix-ui/react-portal": 1.0.3
+ "@radix-ui/react-presence": 1.0.1
+ "@radix-ui/react-primitive": 1.0.3
+ "@radix-ui/react-slot": 1.0.2
+ "@radix-ui/react-use-controllable-state": 1.0.1
+ aria-hidden: ^1.1.1
+ react-remove-scroll: 2.5.5
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 45eb0cb8c9b74714e4a3027055e3d1e8387f6c02c6822aa9711bbc3dffa2f6981385ce55909102987fbf4a48a85c10989b80ad71e80229dae0745ccd6b2e3e76
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-dialog@npm:1.0.5, @radix-ui/react-dialog@npm:^1.0.4":
version: 1.0.5
resolution: "@radix-ui/react-dialog@npm:1.0.5"
@@ -2354,6 +2387,30 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-dismissable-layer@npm:1.0.4":
+ version: 1.0.4
+ resolution: "@radix-ui/react-dismissable-layer@npm:1.0.4"
+ dependencies:
+ "@babel/runtime": ^7.13.10
+ "@radix-ui/primitive": 1.0.1
+ "@radix-ui/react-compose-refs": 1.0.1
+ "@radix-ui/react-primitive": 1.0.3
+ "@radix-ui/react-use-callback-ref": 1.0.1
+ "@radix-ui/react-use-escape-keydown": 1.0.3
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: a7b9695092cd4109a7b4a4a66b7f634c42d4f39aa0893621a8ee5e8bc90f8ae27e741df66db726c341a60d2115e3f813520fee1f5cc4fb05d77914b4ade3819f
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-dismissable-layer@npm:1.0.5":
version: 1.0.5
resolution: "@radix-ui/react-dismissable-layer@npm:1.0.5"
@@ -2419,6 +2476,28 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-focus-scope@npm:1.0.3":
+ version: 1.0.3
+ resolution: "@radix-ui/react-focus-scope@npm:1.0.3"
+ dependencies:
+ "@babel/runtime": ^7.13.10
+ "@radix-ui/react-compose-refs": 1.0.1
+ "@radix-ui/react-primitive": 1.0.3
+ "@radix-ui/react-use-callback-ref": 1.0.1
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: bfff46919666c122f5b812ee427494ae8408c0eebee30337bd2ce0eedf539f0feaa242f790304ef9df15425b837010ffc6061ce467bedd2c5fd9373bee2b95da
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-focus-scope@npm:1.0.4":
version: 1.0.4
resolution: "@radix-ui/react-focus-scope@npm:1.0.4"
@@ -2577,6 +2656,26 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-portal@npm:1.0.3":
+ version: 1.0.3
+ resolution: "@radix-ui/react-portal@npm:1.0.3"
+ dependencies:
+ "@babel/runtime": ^7.13.10
+ "@radix-ui/react-primitive": 1.0.3
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: baf295bbbf09ead37b64ee1dc025a6a540960f5e60552766d78f6065504c67d4bcf49fad5e2073617d9a3011daafad625aa3bd1da7a886c704833b22a49e888f
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-portal@npm:1.0.4, @radix-ui/react-portal@npm:^1.0.3":
version: 1.0.4
resolution: "@radix-ui/react-portal@npm:1.0.4"
@@ -14692,7 +14791,7 @@ turbo@latest:
"@medusajs/icons": ^1.2.0
"@medusajs/ui": ^3.0.0
"@medusajs/ui-preset": ^1.1.3
- "@radix-ui/react-dialog": ^1.0.4
+ "@radix-ui/react-dialog": 1.0.4
"@radix-ui/react-scroll-area": ^1.0.4
"@radix-ui/react-tabs": ^1.0.4
"@types/node": 20.4.9