docs: add routing page (#9550)

- Add a new homepage to `book` project for the routing page
- Move all main doc pages to be under `/v2/learn` (and added redirects + fixed links across docs)
- Other: add admin components to resources dropdown + fixes to search on mobile.

Closes DX-955

Preview: https://docs-v2-git-docs-router-page-medusajs.vercel.app/v2
This commit is contained in:
Shahed Nasser
2024-10-18 08:24:34 +00:00
committed by GitHub
parent 7a47f5211d
commit 0a37675f0e
223 changed files with 2549 additions and 696 deletions
@@ -0,0 +1,70 @@
export const metadata = {
title: `${pageNumber} Module's Container`,
}
# {metadata.title}
In this chapter, you'll learn about the module's container and how to resolve resources in that container.
## Module's Container
Since modules are isolated, 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 other resources registered in the module's container.
### List of Registered Resources
Find a list of resources or dependencies registered in a module's container in [this Learning Resources reference](!resoures!/medusa-container-resources).
---
## Resolve Resources
### Services
A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container.
For example:
```ts highlights={[["4"], ["10"]]}
import { Logger } from "@medusajs/framework/types"
type InjectedDependencies = {
logger: Logger
}
export default class HelloModuleService {
protected logger_: Logger
constructor({ logger }: InjectedDependencies) {
this.logger_ = logger
this.logger_.info("[HelloModuleService]: Hello World!")
}
// ...
}
```
### Loader
A loader function accepts as a parameter an object having the property `container`. Its value is the module's container used to resolve resources.
For example:
```ts highlights={[["9"]]}
import {
LoaderOptions,
} from "@medusajs/framework/types"
import {
ContainerRegistrationKeys
} from "@medusajs/framework/utils"
export default async function helloWorldLoader({
container,
}: LoaderOptions) {
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
logger.info("[helloWorldLoader]: Hello, World!")
}
```
@@ -0,0 +1,464 @@
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: `${pageNumber} Perform Database Operations in a Service`,
}
# {metadata.title}
In this chapter, you'll learn how to perform database operations in a module's service.
<Note>
This chapter is intended for more advanced database use-cases where you need more control over queries and operations. For basic database operations, such as creating or retrieving data of a model, use the [Service Factory](../service-factory/page.mdx) instead.
</Note>
## Run Queries
[MikroORM's entity manager](https://mikro-orm.io/docs/entity-manager) is a class that has methods to run queries on the database and perform operations.
Medusa provides an `InjectManager` decorator imported from `@medusajs/utils` that injects a service's method with a [forked entity manager](https://mikro-orm.io/docs/identity-map#forking-entity-manager).
So, to run database queries in a service:
1. Add the `InjectManager` decorator to the method.
2. Add as a last parameter an optional `sharedContext` parameter that has the `MedusaContext` decorator imported from `@medusajs/utils`. This context holds database-related context, including the manager injected by `InjectManager`
For example, in your service, add the following methods:
export const methodsHighlight = [
["11", "getCount", "Retrieves the number of records in `my_custom` using the `count` method."],
["18", "getCountSql", "Retrieves the number of records in `my_custom` using the `execute` method."]
]
```ts highlights={methodsHighlight}
// other imports...
import {
InjectManager,
MedusaContext,
} from "@medusajs/framework/utils"
class HelloModuleService {
// ...
@InjectManager()
async getCount(
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<number> {
return await sharedContext.manager.count("my_custom")
}
@InjectManager()
async getCountSql(
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<number> {
const data = await sharedContext.manager.execute(
"SELECT COUNT(*) as num FROM my_custom"
)
return parseInt(data[0].num)
}
}
```
You add two methods `getCount` and `getCountSql` that have the `InjectManager` decorator. Each of the methods also accept the `sharedContext` parameter which has the `MedusaContext` decorator.
The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the @mikro-orm/knex package](https://mikro-orm.io/api/5.9/knex/class/EntityManager).
You use the manager in the `getCount` method to retrieve the number of records in a table, and in the `getCountSql` to run a PostgreSQL query that retrieves the count.
<Note>
Refer to [MikroORM's reference](https://mikro-orm.io/api/5.9/knex/class/EntityManager) for a full list of the entity manager's methods.
</Note>
---
## Execute Operations in Transactions
To wrap database operations in a transaction, you create two methods:
1. A private or protected method that's wrapped in a transaction. To wrap it in a transaction, you use the `InjectTransactionManager` decorator imported from `@medusajs/utils`.
2. A public method that calls the transactional method. You use on it the `InjectManager` decorator as explained in the previous section.
Both methods must accept as a last parameter an optional `sharedContext` parameter that has the `MedusaContext` decorator imported from `@medusajs/utils`. It holds database-related contexts passed through the Medusa application.
For example:
export const opHighlights = [
["11", "InjectTransactionManager", "A decorator that injects the a transactional entity manager into the `sharedContext` parameter."],
["17", "MedusaContext", "A decorator to use Medusa's shared context."],
["20", "nativeUpdate", "Update a record."],
["31", "execute", "Retrieve the updated record."],
["38", "InjectManager", "A decorator that injects a forked entity manager into the context."],
]
```ts highlights={opHighlights}
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
// ...
@InjectTransactionManager()
protected async update_(
input: {
id: string,
name: string
},
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<any> {
const transactionManager = sharedContext.transactionManager
await transactionManager.nativeUpdate(
"my_custom",
{
id: input.id,
},
{
name: input.name,
}
)
// retrieve again
const updatedRecord = await transactionManager.execute(
`SELECT * FROM my_custom WHERE id = '${input.id}'`
)
return updatedRecord
}
@InjectManager()
async update(
input: {
id: string,
name: string
},
@MedusaContext() sharedContext?: Context<EntityManager>
) {
return await this.update_(input, sharedContext)
}
}
```
The `HelloModuleService` has two methods:
- A protected `update_` that performs the database operations inside a transaction.
- A public `update` that executes the transactional protected method.
The shared context's `transactionManager` property holds the transactional entity manager (injected by `InjectTransactionManager`) that you use to perform database operations.
<Note>
Refer to [MikroORM's reference](https://mikro-orm.io/api/5.9/knex/class/EntityManager) for a full list of the entity manager's methods.
</Note>
### Why Wrap a Transactional Method
The variables in the transactional method (for example, `update_`) hold values that are uncomitted to the database. They're only committed once the method finishes execution.
So, if in your method you perform database operations, then use their result to perform other actions, such as connect to a third-party service, you'll be working with uncommitted data.
By placing only the database operations in a method that has the `InjectTransactionManager` and using it in a wrapper method, the wrapper method receives the committed result of the transactional method.
<Note title="Optimization Tip">
This is also useful if you perform heavy data normalization outside of the database operations. In that case, you don't hold the transaction for a longer time than needed.
</Note>
For example, the `update` method could be changed to the following:
```ts
// other imports...
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
// ...
@InjectManager()
async update(
input: {
id: string,
name: string
},
@MedusaContext() sharedContext?: Context<EntityManager>
) {
const newData = await this.update_(input, sharedContext)
await sendNewDataToSystem(newData)
return newData
}
}
```
In this case, only the `update_` method is wrapped in a transaction. The returned value `newData` holds the committed result, which can be used for other operations, such as passed to a `sendNewDataToSystem` method.
### Using Methods in Transactional Methods
If your transactional method uses other methods that accept a Medusa context, pass the shared context to those method.
For example:
```ts
// other imports...
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
// ...
@InjectTransactionManager()
protected async anotherMethod(
@MedusaContext() sharedContext?: Context<EntityManager>
) {
// ...
}
@InjectTransactionManager()
protected async update_(
input: {
id: string,
name: string
},
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<any> {
anotherMethod(sharedContext)
}
}
```
You use the `anotherMethod` transactional method in the `update_` transactional method, so you pass it the shared context.
The `anotherMethod` now runs in the same transaction as the `update_` method.
---
## Configure Transactions
To configure the transaction, such as its [isolation level](https://www.postgresql.org/docs/current/transaction-iso.html), use the `baseRepository` dependency registered in your module's container.
The `baseRepository` is an instance of a repository class that provides methods to create transactions, run database operations, and more.
The `baseRepository` has a `transaction` method that allows you to run a function within a transaction and configure that transaction.
For example, resolve the `baseRepository` in your service's constructor:
<CodeTabs group="service-type">
<CodeTab label="Extending Service Factory" value="service-factory">
```ts highlights={[["14"]]}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
import { DAL } from "@medusajs/framework/types"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
}
class HelloModuleService extends MedusaService({
MyCustom,
}){
protected baseRepository_: DAL.RepositoryService
constructor({ baseRepository }: InjectedDependencies) {
super(...arguments)
this.baseRepository_ = baseRepository
}
}
export default HelloModuleService
```
</CodeTab>
<CodeTab label="Without Service Factory" value="no-service-factory">
```ts highlights={[["10"]]}
import { DAL } from "@medusajs/framework/types"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
}
class HelloModuleService {
protected baseRepository_: DAL.RepositoryService
constructor({ manager }: InjectedDependencies) {
this.baseRepository_ = baseRepository
}
}
export default HelloModuleService
```
</CodeTab>
</CodeTabs>
Then, add the following method that uses it:
export const repoHighlights = [
["20", "transaction", "Wrap the function parameter in a transaction."]
]
```ts highlights={repoHighlights}
// ...
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
// ...
@InjectTransactionManager()
protected async update_(
input: {
id: string,
name: string
},
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<any> {
return await this.baseRepository_.transaction(
async (transactionManager) => {
await transactionManager.nativeUpdate(
"my_custom",
{
id: input.id,
},
{
name: input.name,
}
)
// retrieve again
const updatedRecord = await transactionManager.execute(
`SELECT * FROM my_custom WHERE id = '${input.id}'`
)
return updatedRecord
},
{
transaction: sharedContext.transactionManager,
}
)
}
@InjectManager()
async update(
input: {
id: string,
name: string
},
@MedusaContext() sharedContext?: Context<EntityManager>
) {
return await this.update_(input, sharedContext)
}
}
```
The `update_` method uses the `baseRepository_.transaction` method to wrap a function in a transaction.
The function parameter receives a transactional entity manager as a parameter. Use it to perform the database operations.
The `baseRepository_.transaction` method also receives as a second parameter an object of options. You must pass in it the `transaction` property and set its value to the `sharedContext.transactionManager` property so that the function wrapped in the transaction uses the injected transaction manager.
<Note>
Refer to [MikroORM's reference](https://mikro-orm.io/api/5.9/knex/class/EntityManager) for a full list of the entity manager's methods.
</Note>
### Transaction Options
The second parameter of the `baseRepository_.transaction` method is an object of options that accepts the following properties:
1. `transaction`: Set the transactional entity manager passed to the function. You must provide this option as explained in the previous section.
```ts highlights={[["16"]]}
// other imports...
import { EntityManager } from "@mikro-orm/knex"
class HelloModuleService {
// ...
@InjectTransactionManager()
async update_(
input: {
id: string,
name: string
},
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<any> {
return await this.baseRepository_.transaction<EntityManager>(
async (transactionManager) => {
// ...
},
{
transaction: sharedContext.transactionManager,
}
)
}
}
```
2. `isolationLevel`: Sets the transaction's [isolation level](https://www.postgresql.org/docs/current/transaction-iso.html). Its values can be:
- `read committed`
- `read uncommitted`
- `snapshot`
- `repeatable read`
- `serializable`
```ts highlights={[["19"]]}
// other imports...
import { IsolationLevel } from "@mikro-orm/core"
class HelloModuleService {
// ...
@InjectTransactionManager()
async update_(
input: {
id: string,
name: string
},
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<any> {
return await this.baseRepository_.transaction<EntityManager>(
async (transactionManager) => {
// ...
},
{
isolationLevel: IsolationLevel.READ_COMMITTED,
}
)
}
}
```
3. `enableNestedTransactions`: (default: `false`) whether to allow using nested transactions.
- If `transaction` is provided and this is disabled, the manager in `transaction` is re-used.
```ts highlights={[["16"]]}
class HelloModuleService {
// ...
@InjectTransactionManager()
async update_(
input: {
id: string,
name: string
},
@MedusaContext() sharedContext?: Context<EntityManager>
): Promise<any> {
return await this.baseRepository_.transaction<EntityManager>(
async (transactionManager) => {
// ...
},
{
enableNestedTransactions: false,
}
)
}
}
```
@@ -0,0 +1,113 @@
export const metadata = {
title: `${pageNumber} Module Isolation`,
}
# {metadata.title}
In this chapter, you'll learn how modules are isolated, and what that means for your custom development.
<Note title="Summary">
- Modules can't access resources, such as services or data models, from other modules.
- Use Medusa's linking concepts, as explained in the [Module Links chapters](../../module-links/page.mdx), to extend a module's data models and retrieve data across modules.
</Note>
## How are Modules Isolated?
A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module.
For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models.
---
## Why are Modules Isolated
Some of the module isolation's benefits include:
- Integrate your module into any Medusa application without side-effects to your setup.
- Replace existing modules with your custom implementation, if your use case is drastically different.
- Use modules in other environments, such as Edge functions and Next.js apps.
---
## How to Extend Data Model of Another Module?
To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](../../module-links/page.mdx).
---
## How to Use Services of Other Modules?
If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities.
Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output.
### Example
For example, consider you have two modules:
1. A module that stores and manages brands in your application.
2. A module that integrates a third-party Content Management System (CMS).
To sync brands from your application to the third-party system, create the following steps:
export const stepsHighlights = [
["1", "retrieveBrandsStep", "A step that retrieves brands using a brand module."],
["14", "createBrandsInCmsStep", "A step that creates brands using a CMS module."],
["25", "", "Add a compensation function to the step if an error occurs."]
]
```ts title="Example Steps" highlights={stepsHighlights}
const retrieveBrandsStep = createStep(
"retrieve-brands",
async (_, { container }) => {
const brandModuleService = container.resolve(
"brandModuleService"
)
const brands = await brandModuleService.listBrands()
return new StepResponse(brands)
}
)
const createBrandsInCmsStep = createStep(
"create-brands-in-cms",
async ({ brands }, { container }) => {
const cmsModuleService = container.resolve(
"cmsModuleService"
)
const cmsBrands = await cmsModuleService.createBrands(brands)
return new StepResponse(cmsBrands, cmsBrands)
},
async (brands, { container }) => {
const cmsModuleService = container.resolve(
"cmsModuleService"
)
await cmsModuleService.deleteBrands(
brands.map((brand) => brand.id)
)
}
)
```
The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module.
Then, create the following workflow that uses these steps:
```ts title="Example Workflow"
export const syncBrandsWorkflow = createWorkflow(
"sync-brands",
() => {
const brands = retrieveBrandsStep()
updateBrandsInCmsStep({ brands })
}
)
```
You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
@@ -0,0 +1,130 @@
export const metadata = {
title: `${pageNumber} Multiple Services in a Module`,
}
# {metadata.title}
In this chapter, you'll learn how to use multiple services in a module.
## Module's Main and Internal Services
A module has one main service only, which is the service exported in the module's definition.
However, you may use other services in your module to better organize your code or split functionalities. These are called internal services that can be resolved within your module, but not in external resources.
---
## How to Add an Internal Service
### 1. Create Service
To add an internal service, create it in the `services` directory of your module.
For example, create the file `src/modules/hello/services/client.ts` with the following content:
```ts title="src/modules/hello/services/client.ts"
export class ClientService {
async getMessage(): Promise<string> {
return "Hello, World!"
}
}
```
### 2. Export Service in Index
Next, create an `index.ts` file under the `services` directory of the module that exports your internal services.
For example, create the file `src/modules/hello/services/index.ts` with the following content:
```ts title="src/modules/hello/services/index.ts"
export * from "./client"
```
This exports the `ClientService`.
### 3. Resolve Internal Service
Internal services exported in the `services/index.ts` file of your module are now registered in the container and can be resolved in other services in the module as well as loaders.
For example, in your main service:
```ts title="src/modules/hello/service.ts" highlights={[["5"], ["13"]]}
// other imports...
import { ClientService } from "./services"
type InjectedDependencies = {
clientService: ClientService
}
class HelloModuleService extends MedusaService({
MyCustom,
}){
protected clientService_: ClientService
constructor({ clientService }: InjectedDependencies) {
super(...arguments)
this.clientService_ = clientService
}
}
```
You can now use your internal service in your main service.
---
## Resolve Resources in Internal Service
Resolve dependencies from your module's container in the constructor of your internal service.
For example:
```ts
import { Logger } from "@medusajs/framework/types"
type InjectedDependencies = {
logger: Logger
}
export class ClientService {
protected logger_: Logger
constructor({ logger }: InjectedDependencies) {
this.logger_ = logger
}
}
```
---
## Access Module Options
Your internal service can't access the module's options.
To retrieve the module's options, use the `configModule` registered in the module's container, which is the configurations in `medusa-config.ts`.
For example:
```ts
import { ConfigModule } from "@medusajs/framework/types"
import { HELLO_MODULE } from ".."
export type InjectedDependencies = {
configModule: ConfigModule
}
export class ClientService {
protected options: Record<string, any>
constructor({ configModule }: InjectedDependencies) {
const moduleDef = configModule.modules[HELLO_MODULE]
if (typeof moduleDef !== "boolean") {
this.options = moduleDef.options
}
}
}
```
The `configModule` has a `modules` property that includes all registered modules. Retrieve the module's configuration using its registration key.
If its value is not a `boolean`, set the service's options to the module configuration's `options` property.
@@ -0,0 +1,100 @@
export const metadata = {
title: `${pageNumber} Module Options`,
}
# {metadata.title}
In this chapter, youll learn about passing options to your module from the Medusa applications configurations and using them in the modules resources.
## What are Module Options?
A module can receive options to customize or configure its functionality.
For example, if youre creating a module that integrates a third-party service, youll want to receive the integration credentials in the options rather than adding them directly in your code.
---
## How to Pass Options to a Module?
To pass options to a module, add an `options` property to the modules configuration in `medusa-config.ts`.
For example:
```js title="medusa-config.ts"
module.exports = defineConfig({
// ...
modules: [
{
resolve: "./src/modules/hello",
options: {
capitalize: true,
},
},
]
})
```
The `options` propertys value is an object. You can pass any properties you want.
---
## Access Module Options in Main Service
The modules main service receives the module options as a second parameter.
For example:
```ts title="src/modules/hello/service.ts" highlights={[["12"], ["14", "options?: ModuleOptions"], ["17"], ["18"], ["19"]]}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
// recommended to define type in another file
type ModuleOptions = {
capitalize?: boolean
}
export default class HelloModuleService extends MedusaService({
MyCustom,
}){
protected options_: ModuleOptions
constructor({}, options?: ModuleOptions) {
super(...arguments)
this.options_ = options || {
capitalize: false,
}
}
// ...
}
```
---
## Access Module Options in Loader
The object that a modules loaders receive as a parameter has an `options` property holding the module's options.
For example:
```ts title="src/modules/hello/loaders/hello-world.ts" highlights={[["11"], ["12", "ModuleOptions", "The type of expected module options."], ["16"]]}
import {
LoaderOptions,
} from "@medusajs/framework/types"
// recommended to define type in another file
type ModuleOptions = {
capitalize?: boolean
}
export default async function helloWorldLoader({
options,
}: LoaderOptions<ModuleOptions>) {
console.log(
"[HELLO MODULE] Just started the Medusa application!",
options
)
}
```
@@ -0,0 +1,15 @@
export const metadata = {
title: `${pageNumber} Modules Advanced Guides`,
}
# {metadata.title}
In the next chapters, you'll learn more about developing modules and related resources.
By the end of this chapter, you'll know more about:
1. A module's container and how a module is isolated.
2. Passing options to a module.
3. The service factory and the methods it generates.
4. Using a module's service to query and perform actions on the database.
5. Using multiple services in a module.
@@ -0,0 +1,40 @@
export const metadata = {
title: `${pageNumber} Service Constraints`,
}
# {metadata.title}
This chapter lists constraints to keep in mind when creating a service.
## Use Async Methods
Medusa wraps service method executions to inject useful context or transactions. However, since Medusa can't detect whether the method is asynchronus, it always executes methods in the wrapper with the `await` keyword.
For example, if you have a synchronous `getMessage` method, and you use it other resources like workflows, Medusa executes it as an async method:
```ts
await helloModuleService.getMessage()
```
So, make sure your service's methods are always async to avoid unexpected errors or behavior.
```ts highlights={[["8", "", "Method must be async."], ["13", "async", "Correct way of defining the method."]]}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
class HelloModuleService extends MedusaService({
MyCustom,
}){
// Don't
getMessage(): string {
return "Hello, World!"
}
// Do
async getMessage(): Promise<string> {
return "Hello, World!"
}
}
export default HelloModuleService
```
@@ -0,0 +1,299 @@
import { Tabs, TabsContent, TabsContentWrapper, TabsList, TabsTriggerVertical } from "docs-ui"
export const metadata = {
title: `${pageNumber} Service Factory`,
}
# {metadata.title}
In this chapter, youll learn about what the service factory is and how to use it.
## What is the Service Factory?
Medusa provides a service factory that your modules main service can extend.
The service factory generates data management methods for your data models in the database, so you don't have to implement these methods manually.
<Note title="Extend the service factory when" type="success">
Your service provides data-management functionalities of your data models.
</Note>
---
## How to Extend the Service Factory?
Medusa provides the service factory as a `MedusaService` function your service extends. The function creates and returns a service class with generated data-management methods.
For example, create the file `src/modules/hello/service.ts` with the following content:
export const highlights = [
["4", "MedusaService", "The service factory function."],
["5", "MyCustom", "The data models to generate data-management methods for."]
]
```ts title="src/modules/hello/service.ts" highlights={highlights}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
class HelloModuleService extends MedusaService({
MyCustom,
}){
// TODO implement custom methods
}
export default HelloModuleService
```
### MedusaService Parameters
The `MedusaService` function accepts one parameter, which is an object of data models to generate data-management methods for.
In the example above, since the `HelloModuleService` extends `MedusaService`, it has methods to manage the `MyCustom` data model, such as `createMyCustoms`.
### Generated Methods
The service factory generates methods to manage the records of each of the data models provided in the first parameter in the database.
The method's names are the operation's name, suffixed by the data model's key in the object parameter passed to `MedusaService`.
For example, the following methods are generated for the service above:
<Note>
Find a complete reference of each of the methods in [this documentation](!resources!/service-factory-reference)
</Note>
<Tabs defaultValue="listMyCustoms" layoutType="vertical" className="mt-2">
<TabsList>
<TabsTriggerVertical value="listMyCustoms">listMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="listAndCountMyCustoms">listAndCount</TabsTriggerVertical>
<TabsTriggerVertical value="retrieveMyCustom">retrieveMyCustom</TabsTriggerVertical>
<TabsTriggerVertical value="createMyCustoms">createMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="updateMyCustoms">updateMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="deleteMyCustoms">deleteMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="softDeleteMyCustoms">softDeleteMyCustoms</TabsTriggerVertical>
<TabsTriggerVertical value="restoreMyCustoms">restoreMyCustoms</TabsTriggerVertical>
</TabsList>
<TabsContentWrapper className="[&_h3]:!mt-0">
<TabsContent value="listMyCustoms">
### listMyCustoms
This method retrieves an array of records based on filters and pagination configurations.
For example:
```ts
const myCustoms = await helloModuleService
.listMyCustoms()
// with filters
const myCustoms = await helloModuleService
.listMyCustoms({
id: ["123"]
})
```
</TabsContent>
<TabsContent value="listAndCountMyCustoms">
### listAndCountMyCustoms
This method retrieves a tuple of an array of records and the total count of available records based on the filters and pagination configurations provided.
For example:
```ts
const [
myCustoms,
count
] = await helloModuleService.listAndCountMyCustoms()
// with filters
const [
myCustoms,
count
] = await helloModuleService.listAndCountMyCustoms({
id: ["123"]
})
```
</TabsContent>
<TabsContent value="retrieveMyCustom">
### retrieveMyCustom
This method retrieves a record by its ID.
For example:
```ts
const myCustom = await helloModuleService
.retrieveMyCustom("123")
```
</TabsContent>
<TabsContent value="createMyCustoms">
### createMyCustoms
This method creates and retrieves records of the data model.
For example:
```ts
const myCustom = await helloModuleService
.createMyCustoms({
name: "test"
})
// create multiple
const myCustoms = await helloModuleService
.createMyCustoms([
{
name: "test"
},
{
name: "test 2"
},
])
```
</TabsContent>
<TabsContent value="updateMyCustoms">
### updateMyCustoms
This method updates and retrieves records of the data model.
For example:
```ts
const myCustom = await helloModuleService
.updateMyCustoms({
id: "123",
name: "test"
})
// update multiple
const myCustoms = await helloModuleService
.updateMyCustoms([
{
id: "123",
name: "test"
},
{
id: "321",
name: "test 2"
},
])
// use filters
const myCustoms = await helloModuleService
.updateMyCustoms([
{
selector: {
id: ["123", "321"]
},
data: {
name: "test"
}
},
])
```
</TabsContent>
<TabsContent value="deleteMyCustoms">
### deleteMyCustoms
This method deletes records by an ID or filter.
For example:
```ts
await helloModuleService.deleteMyCustoms("123")
// delete multiple
await helloModuleService.deleteMyCustoms([
"123", "321"
])
// use filters
await helloModuleService.deleteMyCustoms({
selector: {
id: ["123", "321"]
}
})
```
</TabsContent>
<TabsContent value="softDeleteMyCustoms">
### softDeleteMyCustoms
This method soft-deletes records using an array of IDs or an object of filters.
For example:
```ts
await helloModuleService.softDeleteMyCustoms("123")
// soft-delete multiple
await helloModuleService.softDeleteMyCustoms([
"123", "321"
])
// use filters
await helloModuleService.softDeleteMyCustoms({
id: ["123", "321"]
})
```
</TabsContent>
<TabsContent value="restoreMyCustoms">
### restoreMyCustoms
This method restores soft-deleted records using an array of IDs or an object of filters.
For example:
```ts
await helloModuleService.restoreMyCustoms([
"123", "321"
])
// use filters
await helloModuleService.restoreMyCustoms({
id: ["123", "321"]
})
```
</TabsContent>
</TabsContentWrapper>
</Tabs>
### Using a Constructor
If you implement the `constructor` of your service, make sure to call `super` passing it `...arguments`.
For example:
```ts highlights={[["8"]]}
import { MedusaService } from "@medusajs/framework/utils"
import MyCustom from "./models/my-custom"
class HelloModuleService extends MedusaService({
MyCustom,
}){
constructor() {
super(...arguments)
}
}
export default HelloModuleService
```