chore: reorganize docs apps (#7228)
* reorganize docs apps * add README * fix directory * add condition for old docs
This commit is contained in:
1 parent
224ebb2154
commit
4fe28f5a95
6187 files changed
+601447
-598226
No files matched your search
@@ -0,0 +1,57 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Admin Customizations`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to customize the Medusa Admin dashboard.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Admin customizations are coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
The Medusa Admin is installed in your Medusa application and runs at port `7001` when you start the Medusa application.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Example: Create a Widget
|
||||
|
||||
A widget is a React component that can be injected into an existing page in the admin dashboard.
|
||||
|
||||
For example, create the file `src/admin/widgets/product-widget.tsx` with the following content:
|
||||
|
||||
```tsx title="src/admin/widgets/product-widget.tsx"
|
||||
import type { WidgetConfig } from "@medusajs/admin"
|
||||
|
||||
const ProductWidget = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Product Widget</h2>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: WidgetConfig = {
|
||||
zone: "product.details.after",
|
||||
}
|
||||
|
||||
export default ProductWidget
|
||||
```
|
||||
|
||||
This inserts a widget with the text “Product Widget” at the end of a product’s details page.
|
||||
|
||||
### Test the Widget
|
||||
|
||||
To test out the widget, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, open a product’s details page. You’ll find your custom widget at the bottom of the page.
|
||||
@@ -0,0 +1,63 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} API Routes`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn what API Routes are and how to create them.
|
||||
|
||||
## What is an API Route?
|
||||
|
||||
An API Route is a REST API endpoint. It exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems.
|
||||
|
||||
The Medusa core application provides a set of admin and store API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.
|
||||
|
||||
---
|
||||
|
||||
## How to Create an API Route
|
||||
|
||||
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…).
|
||||
|
||||
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:
|
||||
|
||||
```ts title="src/api/store/hello-world/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "[GET] Hello world!",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Test API Route
|
||||
|
||||
To test the API route above, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, send a `GET` request to the `/store/hello-world` API Route:
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/store/hello-world" testApiMethod="GET"
|
||||
curl http://localhost:9000/store/hello-world
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
<Note title="Use API routes when" type="success">
|
||||
|
||||
- You're exposing custom functionality to be used in the admin dashboard or an external application, such as a storefront.
|
||||
|
||||
</Note>
|
||||
@@ -0,0 +1,48 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Commerce Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn about Medusa's commerce modules.
|
||||
|
||||
## What is a Commerce Module?
|
||||
|
||||
Medusa provides all its commerce features as separate modules, such as the Product Module, Cart Module, or Order Module. These modules and your custom modules are interchangeable in the Medusa application, making Medusa’s architecture more flexible.
|
||||
|
||||
Refer to [this reference](!resources!/commerce-modules) for a full list of commerce modules in Medusa.
|
||||
|
||||
---
|
||||
|
||||
## Resolve Commerce Module Services
|
||||
|
||||
Similarly to your custom module, a commerce module's main service is registered in the Medusa container. So, you can resolve it in your resources, such as API route, to use its functionality.
|
||||
|
||||
For example, you saw this code snippet in the [Medusa container chapter](../medusa-container/page.mdx):
|
||||
|
||||
```ts highlights={[["13"]]}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const productModuleService: IProductModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const [, count] = await productModuleService.listAndCount()
|
||||
|
||||
res.json({
|
||||
count,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,228 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Models`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn what data models are and how to create a data model.
|
||||
|
||||
## What is a Data Model?
|
||||
|
||||
A data model is a class that represents a table in the database. A data model is created in a module. You can then create a service that manages that data model.
|
||||
|
||||
Data models are based on [MikroORM](https://mikro-orm.io/docs/quick-start). So, you can use its decorators, types, and utilities when creating a model.
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Data Model
|
||||
|
||||
<Note title="Steps Summary">
|
||||
|
||||
1. Create data model class in a module.
|
||||
2. Generate migration for the data model.
|
||||
3. Add migration scripts to the module's definition.
|
||||
4. Run migration to add table for data model in the database.
|
||||
|
||||
</Note>
|
||||
|
||||
A data model is a class created in a TypeScript or JavaScript file created in a module under its `models` directory.
|
||||
|
||||
For example, create the file `src/modules/hello/models/my-custom.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/hello/models/my-custom.ts"
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
OnInit,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
export class MyCustom {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "mc")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
OnInit() {
|
||||
this.id = generateEntityId(this.id, "mc")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This defines a new data model `MyCustom` with the fields `id` and `name`.
|
||||
|
||||
The `onCreate` method generates an ID for the data model's record when it's created. The `onInit` method sets the ID when the record is loaded.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The `generateEntityId` utility method prefixes the `id` of a record with the string provided in the second parameter. This follows Medusa's conventions of creating IDs.
|
||||
|
||||
</Note>
|
||||
|
||||
### Create a Migration
|
||||
|
||||
After creating the data model, you must create a migration that creates a table in your database for this data model.
|
||||
|
||||
A migration 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.
|
||||
|
||||
<Details summaryContent="Generate with MikroORM">
|
||||
MikroORM provides a CLI tool that helps you generate migrations. To use it:
|
||||
|
||||
1. Create the file `src/modules/hello/mikro-orm.config.dev.ts` with the following content:
|
||||
|
||||
```ts
|
||||
import "dotenv/config"
|
||||
import path from "path"
|
||||
import { TSMigrationGenerator } from "@medusajs/utils"
|
||||
import { MyCustom } from "./models/my-custom"
|
||||
|
||||
module.exports = {
|
||||
entities: [MyCustom],
|
||||
schema: "public",
|
||||
clientUrl: "postgres://postgres@localhost/medusa-hello",
|
||||
type: "postgresql",
|
||||
migrations: {
|
||||
path: path.join(__dirname, "migrations"),
|
||||
generator: TSMigrationGenerator,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
2. Run the following command in the root directory of your Medusa application:
|
||||
|
||||
```bash
|
||||
npx cross-env MIKRO_ORM_CLI=./src/modules/hello/mikro-orm.config.dev.ts mikro-orm migration:create
|
||||
```
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
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.
|
||||
|
||||
</Note>
|
||||
|
||||
After running the command, a new file is created under the `src/modules/hello/migrations` directory. This file holds `up` and `down` methods that define the actions to execute when running and reverting the migration respectively.
|
||||
|
||||
</Details>
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240429090012 extends Migration {
|
||||
|
||||
async up(): Promise<void> {
|
||||
this.addSql("create table if not exists \"my_custom\" (\"id\" varchar(255) not null, \"name\" text not null, constraint \"my_custom_pkey\" primary key (\"id\"));")
|
||||
}
|
||||
|
||||
async down(): Promise<void> {
|
||||
this.addSql("drop table if exists \"my_custom\" cascade;")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
In the `up` method, you create the table `my_custom` and define its columns. In the `down` method, you drop the table.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The queries performed in each of the methods use PostgreSQL syntax.
|
||||
|
||||
</Note>
|
||||
|
||||
### 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.
|
||||
|
||||
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.
|
||||
|
||||
Change the content of `src/modules/hello.index.ts` that you created in a [previous chapter](../modules-and-services/page.mdx) to the following:
|
||||
|
||||
```ts title="src/modules/hello.index.ts" highlights={[["2"], ["6"], ["10"], ["12"], ["14"], ["18"], ["22"], ["28"], ["36"]]}
|
||||
import HelloModuleService from "./service"
|
||||
// add necessary imports
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { MyCustom } from "./models/my-custom"
|
||||
|
||||
// define useful constants
|
||||
const moduleName = "hello"
|
||||
const pathToMigrations = __dirname + "/migrations"
|
||||
|
||||
// assemble object to pass to utility functions
|
||||
const migrationScriptOptions = {
|
||||
// the module's name
|
||||
moduleName,
|
||||
// the data models of the modules
|
||||
models: {
|
||||
MyCustom,
|
||||
},
|
||||
// the path to the migrations directory
|
||||
pathToMigrations,
|
||||
}
|
||||
|
||||
// create and export the script that runs migrations
|
||||
export const runMigrations = ModulesSdkUtils
|
||||
.buildMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
// create and export the script that reverts migrations
|
||||
export const revertMigration = ModulesSdkUtils
|
||||
.buildRevertMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
export default {
|
||||
service: HelloModuleService,
|
||||
// add the run and revert migration scripts to the module's definition
|
||||
runMigrations,
|
||||
revertMigration,
|
||||
}
|
||||
```
|
||||
|
||||
After importing `ModulesSdkUtils`, you use its `buildMigrationScript` function to create the script that runs the migration, and its `buildRevertMigrationScript` function to create the script that reverts the migration.
|
||||
|
||||
Both the `buildMigrationScript` and `buildRevertMigrationScript` accept the same object type as a parameter, which has the following properties:
|
||||
|
||||
- `moduleName`: The name of the module that the migrations belong to.
|
||||
- `models`: An object of the module's data models.
|
||||
- `pathToMigrations`: The path to the `migrations` directory.
|
||||
|
||||
Both created scripts must be exported in the file and within the module's definition object.
|
||||
|
||||
### Run Migration
|
||||
|
||||
To reflect the changes in the migration, transpile your source files using the `build` command, then run the `migration` command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
If ran successfully, the `my_custom` table will be created in the database.
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
<Note title="Use data models when" type="success">
|
||||
|
||||
- You want to store data related to your customization in the database.
|
||||
|
||||
</Note>
|
||||
|
||||
<Note title="Don't use data models if" type="error">
|
||||
|
||||
- You want to store simple key-value pairs related to an existing data model. Instead, use the `metadata` field that most existing models have, which is an object of custom key-value pairs.
|
||||
|
||||
</Note>
|
||||
@@ -0,0 +1,116 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Events and Subscribers`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to handle events with subscribers.
|
||||
|
||||
## What is an Event?
|
||||
|
||||
When an action is performed in Medusa, such as creating a product, the Medusa application emits an event. You can listen to those events and perform an asynchronous action.
|
||||
|
||||
---
|
||||
|
||||
## What is a Subscriber?
|
||||
|
||||
A subscriber is a function executed whenever its associated event is emitted.
|
||||
|
||||
The subscriber is created in a TypeScript or JavaScript file under the `src/subscribers` directory.
|
||||
|
||||
For example, create the file `src/subscribers/product-created.ts` with the following content:
|
||||
|
||||
```ts title="src/subscribers/product-created.ts"
|
||||
import {
|
||||
ProductService,
|
||||
type SubscriberConfig,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
// subscriber function
|
||||
export default async function productCreateHandler() {
|
||||
console.log("A product was created")
|
||||
}
|
||||
|
||||
// subscriber config
|
||||
export const config: SubscriberConfig = {
|
||||
event: ProductService.Events.CREATED,
|
||||
}
|
||||
```
|
||||
|
||||
A subscriber file must export:
|
||||
|
||||
- The subscriber function which 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
|
||||
|
||||
To test the subscriber, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, create a product using the Medusa Admin dashboard. Afterward, you’ll see the following messages logged in the terminal:
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
<Note title="Use subscribers when" type="success">
|
||||
|
||||
- You want to perform an action everytime a specific event is emitted in the Medusa application.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Resolve Resources
|
||||
|
||||
The subscriber function accepts an object parameter with the property `container`. Its value is the Medusa container, and you can use it to resolve other resources, such as services.
|
||||
|
||||
For example:
|
||||
|
||||
{/* TODO change how event names are loaded */}
|
||||
|
||||
export const highlights = [
|
||||
["11"],
|
||||
["14", "resolve", "Resolve the `IProductModuleService`."],
|
||||
["14", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/product-created.ts" highlights={highlights}
|
||||
import {
|
||||
ProductService,
|
||||
SubscriberArgs,
|
||||
type SubscriberConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function productCreateHandler({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<Record<string, string>>) {
|
||||
const productModuleService: IProductModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const product = await productModuleService.retrieve(data.id)
|
||||
|
||||
console.log(`The product ${product.title} was created`)
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: ProductService.Events.CREATED,
|
||||
}
|
||||
```
|
||||
|
||||
You use the container to resolve the `IProductModuleService`, then log the title of the created product.
|
||||
@@ -0,0 +1,22 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Important Directories and Files`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about important directories and files in your Medusa application's codebase.
|
||||
|
||||
## src
|
||||
|
||||
This directory is the central place for your custom development. It includes the following sub-directories:
|
||||
|
||||
- `admin`: Holds your admin dashboard's custom components and pages.
|
||||
- `api`: Holds your custom API routes that are added as endpoints in your Medusa application.
|
||||
- `jobs`: Holds your scheduled jobs that run at a specified interval during your Medusa application's runtime.
|
||||
- `loaders`: Holds your loaders that run at the Medusa application's start-up.
|
||||
- `modules`: Holds your custom modules that implement custom business logic.
|
||||
- `subscribers`: Holds your event listeners that are executed asynchronously whenever an event is emitted.
|
||||
|
||||
## medusa-config.js
|
||||
|
||||
This file holds your Medusa configurations, such as your PostgreSQL database configurations or your Medusa Admin plugin configurations.
|
||||
@@ -0,0 +1,87 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Loaders`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about loaders and how to use them.
|
||||
|
||||
## What is a Loader?
|
||||
|
||||
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.
|
||||
|
||||
<Note>
|
||||
|
||||
This guide explains the first approach. This second approach will be explained in later chapters.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
For example, create the file `src/modules/hello/loaders/hello-world.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/hello/loaders/hello-world.ts"
|
||||
export default function helloWorldLoader() {
|
||||
console.log(
|
||||
"[HELLO MODULE] Just started the Medusa application!"
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Export Loader in Module Definition
|
||||
|
||||
You must export your loaders in the module definition. Otherwise, your Medusa application won't run them.
|
||||
|
||||
To do that, import the loader in `src/modules/hello/index.ts` and export it in the module's definition:
|
||||
|
||||
```ts title="src/modules/hello/index.ts"
|
||||
// other imports...
|
||||
import helloWorldLoader from "./loaders/hello-world"
|
||||
|
||||
// ...
|
||||
|
||||
export default {
|
||||
// ...
|
||||
loaders: [helloWorldLoader],
|
||||
}
|
||||
```
|
||||
|
||||
The value of the `loaders` property of the module definition is an array of loader functions.
|
||||
|
||||
### Test Loader
|
||||
|
||||
Start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Among the messages logged in the terminal, you’ll see the following message:
|
||||
|
||||
```bash
|
||||
[HELLO MODULE] Just started the Medusa application!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
<Note title="Use loaders when" type="success">
|
||||
|
||||
- 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.
|
||||
|
||||
</Note>
|
||||
|
||||
<Note title="Don't use loaders if" type="error">
|
||||
|
||||
- You want to perform an action continuously or at a set time pattern in the application. Use scheduled jobs instead, which is explained in the next chapter.
|
||||
|
||||
</Note>
|
||||
@@ -0,0 +1,43 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Medusa container`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about Medusa’s Medusa container and how to use it.
|
||||
|
||||
## 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.
|
||||
|
||||
For example, in a custom API route you can resolve any service registered in the Medusa application using the `scope.resolve` method of the `MedusaRequest` parameter:
|
||||
|
||||
export const highlights = [
|
||||
["13", "resolve", "Resolve the `IProductModuleService`"],
|
||||
["13", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
]
|
||||
|
||||
```ts highlights={highlights}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const productModuleService: IProductModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const [, count] = await productModuleService.listAndCount()
|
||||
|
||||
res.json({
|
||||
count,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
This route handler function resolves the `IProductModuleService` and uses it to return the full count of products in the Medusa application.
|
||||
@@ -0,0 +1,138 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Modules and Services`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about modules, their main service, and how to create them.
|
||||
|
||||
## 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.
|
||||
|
||||
All commerce and architectural customizations and development start with creating a module.
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Module
|
||||
|
||||
<Note title="Steps Summary">
|
||||
|
||||
1. Create module's main service.
|
||||
2. Create module definition.
|
||||
3. Add module to Medusa's configurations.
|
||||
|
||||
</Note>
|
||||
|
||||
Modules are created in a sub-directory of `src/modules`. The directory name is the camel-case name of the module.
|
||||
|
||||
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.
|
||||
|
||||
For example, create the service `src/modules/hello/service.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/hello/service.ts"
|
||||
export default class HelloModuleService {
|
||||
getMessage() {
|
||||
return "Hello, world!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
For example, create the file `src/modules/hello.index.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/hello.index.ts" highlights={[["4", "", "The main service of the module."]]}
|
||||
import HelloModuleService from "./service"
|
||||
|
||||
export default {
|
||||
service: HelloModuleService,
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Register Module in Configurations
|
||||
|
||||
The last step is to register the module in Medusa’s configurations.
|
||||
|
||||
In `medusa-config.js`, add the module to the `modules` object:
|
||||
|
||||
```js title="medusa-config.js" highlights={[["2", "helloModuleService", "The key of the main service to be registered in the Medusa container."]]}
|
||||
const modules = {
|
||||
helloModuleService: {
|
||||
resolve: "./dist/modules/hello",
|
||||
},
|
||||
// other 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.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
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.
|
||||
|
||||
</Note>
|
||||
|
||||
### 4. Test the Module
|
||||
|
||||
Since the module's main service is registered in the Medusa container, you can resolve it in other resources to use its functionalities.
|
||||
|
||||
One way to test the module is to create the API route `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 HelloService from "../../../modules/hello/service"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const helloModuleService: HelloService = req.scope.resolve(
|
||||
"helloModuleService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
message: helloModuleService.getMessage(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Then, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Finally, send a `GET` request to `/store/custom`:
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/store/custom" testApiMethod="GET"
|
||||
curl http://locahost:9000/store/custom
|
||||
```
|
||||
|
||||
You’ll receive the following response:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello, world!"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,29 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Modules Directory Structure`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the expected files and directories in your module.
|
||||
|
||||

|
||||
|
||||
## 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).
|
||||
|
||||
---
|
||||
|
||||
## 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).
|
||||
|
||||
---
|
||||
|
||||
## Other Directories
|
||||
|
||||
The following directories are optional and their content are explained more in the following chapters:
|
||||
|
||||
- `models`: Holds the data models representing tables in the database.
|
||||
- `migrations`: Holds the migration files used to reflect changes on the database.
|
||||
- `loaders`: Holds the scripts to run on the Medusa application's start-up.
|
||||
@@ -0,0 +1,20 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} The Basics`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In the next chapters, you’ll learn about the basic concepts of Medusa. These concepts are central to your Medusa development.
|
||||
|
||||
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.
|
||||
- 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.
|
||||
@@ -0,0 +1,143 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Scheduled Jobs`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about scheduled jobs and how to use them.
|
||||
|
||||
## 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.
|
||||
|
||||
For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day using a scheduled job.
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Scheduled Job?
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Redis](https://redis.io/) is installed and Redis server is running.
|
||||
|
||||
</Note>
|
||||
|
||||
A scheduled job is created in a TypeScript or JavaScript file under the `src/jobs` directory.
|
||||
|
||||
For example, create the file `src/jobs/hello-world.ts` with the following content:
|
||||
|
||||
```ts title="src/jobs/hello-world.ts"
|
||||
import { ScheduledJobConfig } from "@medusajs/medusa"
|
||||
|
||||
// the scheduled-job function
|
||||
export default function () {
|
||||
console.log("Time to say hello world!")
|
||||
}
|
||||
|
||||
// the job's configurations
|
||||
export const config: ScheduledJobConfig = {
|
||||
name: "hello-world",
|
||||
// execute every minute
|
||||
schedule: "* * * * *",
|
||||
}
|
||||
```
|
||||
|
||||
A scheduled job file must export:
|
||||
|
||||
- The function 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/).
|
||||
|
||||
This scheduled job executes every minute and logs into the terminal `Time to say hello world!`.
|
||||
|
||||
### Test Scheduled Jobs
|
||||
|
||||
To test out your scheduled job:
|
||||
|
||||
1. Uncomment the following line in the `medusa-config.js` file:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const projectConfig = {
|
||||
// ...
|
||||
// Uncomment the following lines to enable REDIS
|
||||
redis_url: REDIS_URL,
|
||||
}
|
||||
```
|
||||
|
||||
2. Start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
After a minute, the following message will be logged to the terminal:
|
||||
|
||||
```bash
|
||||
info: Processing scheduled job: hello-world
|
||||
Time to say hello world!
|
||||
```
|
||||
|
||||
The first line indicates that the application is executing the scheduled job. The second line is the text logged by the scheduled job.
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
<Note title="Use scheduled jobs when" type="success">
|
||||
|
||||
- You're executing an action at a specified time interval during application runtime.
|
||||
- The action must be executed automatically.
|
||||
|
||||
</Note>
|
||||
|
||||
<Note title="Don't use scheduled jobs if" type="error">
|
||||
|
||||
- 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.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Resolve Resources
|
||||
|
||||
The scheduled job function receives an object parameter that has a `container` property. Its value is the Medusa container, which you can use to resolve resources in your Medusa application, such as services.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["12", "resolve", "Resolve the `IProductModuleService`."],
|
||||
["12", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
]
|
||||
|
||||
```ts title="src/jobs/hello-world.ts" highlights={highlights}
|
||||
import {
|
||||
ScheduledJobArgs,
|
||||
ScheduledJobConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function ({
|
||||
container,
|
||||
}: ScheduledJobArgs) {
|
||||
const productModuleService: IProductModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const [, count] = await productModuleService.listAndCount()
|
||||
|
||||
console.log(
|
||||
`Time to check products! You have ${count} product(s)`
|
||||
)
|
||||
}
|
||||
|
||||
export const config: ScheduledJobConfig = {
|
||||
name: "hello-world",
|
||||
// execute every minute
|
||||
schedule: "* * * * *",
|
||||
}
|
||||
```
|
||||
|
||||
In the scheduled job function, you resolve the `IProductModuleService` and retrieve the number of products in the store, then log the number in the terminal.
|
||||
@@ -0,0 +1,77 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Services`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn what services are and how to create a service.
|
||||
|
||||
## What is a Service?
|
||||
|
||||
A service is a class that holds methods related to a business logic or commerce functionality. For example, the `ProductService` holds methods to retrieve and manage products.
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Service
|
||||
|
||||
Services are created in a TypeScript or JavaScript file under the `src/services` directory of your Medusa application.
|
||||
|
||||
For example, to create a `HelloWorld` service, create the file `src/services/hello-world.ts` with the following content:
|
||||
|
||||
```ts title="src/services/hello-world.ts"
|
||||
class HelloWorld {
|
||||
// TODO add methods
|
||||
}
|
||||
|
||||
export default HelloWorld
|
||||
```
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
A service's file name must be the kebab-case name of the service without the word `Service`.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
<Note title="Use services when" type="success">
|
||||
|
||||
- You're implementing custom commerce functionality to be reused across the Medusa application.
|
||||
- You're integrating a third-party service, such as an ERP system.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Resolve Resources
|
||||
|
||||
In a service's constructor, you can resolve other services or resources of your Medusa application using the Medusa container.
|
||||
|
||||
For example, you can resolve the `IProductModuleService` to use it in your methods:
|
||||
|
||||
```ts title="src/services/hello-world.ts" highlights={[["10"]]}
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productModuleService: IProductModuleService
|
||||
}
|
||||
|
||||
class HelloWorldService {
|
||||
protected productModuleService: IProductModuleService
|
||||
|
||||
constructor({ productModuleService }: InjectedDependencies) {
|
||||
this.productModuleService = productModuleService
|
||||
}
|
||||
|
||||
async getMessage(): Promise<string> {
|
||||
const [, count] =
|
||||
await this.productModuleService.listAndCount()
|
||||
|
||||
return `You have ${count} products`
|
||||
}
|
||||
}
|
||||
|
||||
export default HelloWorldService
|
||||
```
|
||||
@@ -0,0 +1,268 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Workflows`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about workflows and how to define and execute them.
|
||||
|
||||
## What is a Workflow?
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## How to Create and Execute a Workflow
|
||||
|
||||
### 1. Create the Steps
|
||||
|
||||
A workflow is made of a series of steps. A step is created using the `createStep` utility function.
|
||||
|
||||
Create the file `src/workflows/hello-world.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/hello-world.ts"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async () => {
|
||||
return new StepResponse(`Hello from step one!`)
|
||||
})
|
||||
```
|
||||
|
||||
This creates one step that returns a hello message.
|
||||
|
||||
Steps can accepts input parameters. For example:
|
||||
|
||||
```ts title="src/workflows/hello-world.ts"
|
||||
type WorkflowInput = {
|
||||
name: string
|
||||
}
|
||||
|
||||
const step2 = createStep(
|
||||
"step-2",
|
||||
async ({ name }: WorkflowInput) => {
|
||||
return new StepResponse(`Hello ${name} from step two!`)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Create a Workflow
|
||||
|
||||
Next, create the workflow using the `createWorkflow` function:
|
||||
|
||||
```ts title="src/workflows/hello-world.ts"
|
||||
import {
|
||||
// other imports...
|
||||
createWorkflow,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
// ...
|
||||
|
||||
type WorkflowOutput = {
|
||||
message: string
|
||||
}
|
||||
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
const str1 = step1()
|
||||
// to pass input
|
||||
const str2 = step2(input)
|
||||
|
||||
return {
|
||||
message: str1,
|
||||
}
|
||||
})
|
||||
|
||||
export default myWorkflow
|
||||
|
||||
```
|
||||
|
||||
This creates a `hello-world` workflow. When you create a workflow, it’s constructed but not executed yet.
|
||||
|
||||
### 3. Execute the Workflow
|
||||
|
||||
You can execute a workflow from different places 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.
|
||||
|
||||
To execute the workflow, invoke it passing the Medusa container as a parameter, then use its run method:
|
||||
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import myWorkflow from "../../../workflows/hello-world"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { result } = await myWorkflow(req.scope)
|
||||
.run({
|
||||
input: {
|
||||
name: req.query.name as string,
|
||||
},
|
||||
})
|
||||
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/customer-created.ts" highlights={[["13"], ["14"], ["15"], ["16"], ["17"], ["18"]]}
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
CustomerService,
|
||||
Customer,
|
||||
} from "@medusajs/medusa"
|
||||
import myWorkflow from "../workflows/hello-world"
|
||||
|
||||
export default async function handleCustomerCreate({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<Customer>) {
|
||||
const { result } = await myWorkflow(container)
|
||||
.run({
|
||||
input: {
|
||||
name: data.first_name,
|
||||
},
|
||||
})
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: CustomerService.Events.CREATED,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/message-daily.ts" highlights={[["10"], ["11"], ["12"], ["13"], ["14"], ["15"]]}
|
||||
import {
|
||||
type ScheduledJobConfig,
|
||||
type ScheduledJobArgs,
|
||||
} from "@medusajs/medusa"
|
||||
import myWorkflow from "../workflows/hello-world"
|
||||
|
||||
export default async function handler({
|
||||
container,
|
||||
}: ScheduledJobArgs) {
|
||||
const { result } = await myWorkflow(container)
|
||||
.run({
|
||||
input: {
|
||||
name: "John",
|
||||
},
|
||||
})
|
||||
|
||||
console.log(result.message)
|
||||
}
|
||||
|
||||
export const config: ScheduledJobConfig = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
### 4. Test Workflow
|
||||
|
||||
To test out your workflow, start your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, send a `GET` request to `/store/workflow`:
|
||||
|
||||
```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/workflows"
|
||||
curl http://localhost:9000/store/workflow
|
||||
```
|
||||
|
||||
You’ll receive the following response:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello from step one!"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
<Note title="Use workflows when" type="success">
|
||||
|
||||
- You're defining a flow with interactions across multiple systems and services.
|
||||
- You're defining flows to be used across different resources. For example, if you want to invoke the flow manually through an API Router, but also want to automate its running through a scheduled job.
|
||||
- You want to define how the series of actions are rolled-back when an error occurs.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["14", "resolve", "Resolve the `IProductModuleService`."],
|
||||
["14", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/product-count.ts" highlights={highlights}
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
createWorkflow,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
type WorkflowOutput = {
|
||||
count: number
|
||||
}
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const productModuleService: IProductModuleService =
|
||||
context.container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
return new StepResponse(
|
||||
(await productModuleService.listAndCount())[1]
|
||||
)
|
||||
})
|
||||
|
||||
const myWorkflow = createWorkflow<unknown, WorkflowOutput>(
|
||||
"product-count",
|
||||
function () {
|
||||
const count = step1()
|
||||
|
||||
return {
|
||||
count,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default myWorkflow
|
||||
```
|
||||
Reference in new issue
Block a user