docs: handle product status change in MeiliSearch and Algolia guides (#14141)
* docs: handle product status change in MeiliSearch and Algolia guides * added missing status field
This commit is contained in:
@@ -94138,9 +94138,10 @@ In this step, you'll create a workflow that indexes products in Algolia. In the
|
||||
The workflow has the following steps:
|
||||
|
||||
- [useQueryGraphStep](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve products matching specified filters and pagination parameters.
|
||||
- [syncProductsStep](#syncProductsStep): Index products in Algolia.
|
||||
- [syncProductsStep](#syncProductsStep): Index published products in Algolia.
|
||||
- [deleteProductsFromAlgoliaStep](#deleteProductsFromAlgoliaStep): Delete unpublished products from Algolia.
|
||||
|
||||
Medusa provides the `useQueryGraphStep` in its `@medusajs/medusa/core-flows` package. So, you only need to implement the second step.
|
||||
Medusa provides the `useQueryGraphStep` in its `@medusajs/medusa/core-flows` package. So, you only need to implement the other steps.
|
||||
|
||||
### syncProductsStep
|
||||
|
||||
@@ -94258,6 +94259,64 @@ The compensation function receives two parameters:
|
||||
|
||||
In the compensation function, you resolve the Algolia Module's service from the container. Then, you delete from Algolia the products that were newly indexed, and revert the existing products to their original data.
|
||||
|
||||
#### deleteProductsFromAlgoliaStep
|
||||
|
||||
The `deleteProductsFromAlgoliaStep` step deletes products from Algolia by their IDs.
|
||||
|
||||
Create the step at `src/workflows/steps/delete-products-from-algolia.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/steps/delete-products-from-algolia.ts"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { ALGOLIA_MODULE } from "../../modules/algolia"
|
||||
|
||||
export type DeleteProductsFromAlgoliaWorkflow = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteProductsFromAlgoliaStep = createStep(
|
||||
"delete-products-from-algolia-step",
|
||||
async (
|
||||
{ ids }: DeleteProductsFromAlgoliaWorkflow,
|
||||
{ container }
|
||||
) => {
|
||||
const algoliaModuleService = container.resolve(ALGOLIA_MODULE)
|
||||
|
||||
const existingRecords = await algoliaModuleService.retrieveFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
await algoliaModuleService.deleteFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
|
||||
return new StepResponse(undefined, existingRecords)
|
||||
},
|
||||
async (existingRecords, { container }) => {
|
||||
if (!existingRecords) {
|
||||
return
|
||||
}
|
||||
const algoliaModuleService = container.resolve(ALGOLIA_MODULE)
|
||||
|
||||
await algoliaModuleService.indexData(
|
||||
existingRecords as unknown as Record<string, unknown>[],
|
||||
"product"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The step receives the IDs of the products to delete as an input.
|
||||
|
||||
In the step, you resolve the Algolia Module's service and retrieve the existing records from Algolia. This is useful to revert the deletion if an error occurs.
|
||||
|
||||
Then, you delete the products from Algolia and pass the existing records to the compensation function.
|
||||
|
||||
In the compensation function, you reindex the existing records if an error occurs.
|
||||
|
||||
### Add Sync Products Workflow
|
||||
|
||||
You can now create the workflow that syncs the products to Algolia.
|
||||
@@ -94272,7 +94331,7 @@ import {
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
import { syncProductsStep, SyncProductsStepInput } from "./steps/sync-products"
|
||||
import { ProductStatus } from "@medusajs/framework/utils"
|
||||
import { deleteProductsFromAlgoliaStep } from "./steps/delete-products-from-algolia"
|
||||
|
||||
type SyncProductsWorkflowInput = {
|
||||
filters?: Record<string, unknown>
|
||||
@@ -94283,15 +94342,7 @@ type SyncProductsWorkflowInput = {
|
||||
export const syncProductsWorkflow = createWorkflow(
|
||||
"sync-products",
|
||||
({ filters, limit, offset }: SyncProductsWorkflowInput) => {
|
||||
const productFilters = transform({
|
||||
filters,
|
||||
}, (data) => {
|
||||
return {
|
||||
status: ProductStatus.PUBLISHED,
|
||||
...data.filters,
|
||||
}
|
||||
})
|
||||
const { data, metadata } = useQueryGraphStep({
|
||||
const { data: products, metadata } = useQueryGraphStep({
|
||||
entity: "product",
|
||||
fields: [
|
||||
"id",
|
||||
@@ -94304,20 +94355,49 @@ export const syncProductsWorkflow = createWorkflow(
|
||||
"categories.handle",
|
||||
"tags.id",
|
||||
"tags.value",
|
||||
"status",
|
||||
],
|
||||
pagination: {
|
||||
take: limit,
|
||||
skip: offset,
|
||||
},
|
||||
filters: productFilters,
|
||||
filters,
|
||||
})
|
||||
|
||||
const {
|
||||
publishedProducts,
|
||||
unpublishedProductsToDelete,
|
||||
} = transform({
|
||||
products,
|
||||
}, (data) => {
|
||||
const publishedProducts: SyncProductsStepInput["products"] = []
|
||||
const unpublishedProductsToDelete: string[] = []
|
||||
|
||||
data.products.forEach((product) => {
|
||||
if (product.status === "published") {
|
||||
const { status, ...rest } = product
|
||||
publishedProducts.push(rest as SyncProductsStepInput["products"][0])
|
||||
} else {
|
||||
unpublishedProductsToDelete.push(product.id)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
publishedProducts,
|
||||
unpublishedProductsToDelete,
|
||||
}
|
||||
})
|
||||
|
||||
syncProductsStep({
|
||||
products: data,
|
||||
} as SyncProductsStepInput)
|
||||
products: publishedProducts,
|
||||
})
|
||||
|
||||
deleteProductsFromAlgoliaStep({
|
||||
ids: unpublishedProductsToDelete,
|
||||
})
|
||||
|
||||
return new WorkflowResponse({
|
||||
products: data,
|
||||
products,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
@@ -94331,7 +94411,9 @@ It accepts as a second parameter a constructor function, which is the workflow's
|
||||
In the workflow's constructor function, you:
|
||||
|
||||
1. Execute `useQueryGraphStep` to retrieve products from Medusa's database. This step uses Medusa's [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) tool to retrieve data across modules. You pass it the pagination and filter parameters you received in the input.
|
||||
2. Execute `syncProductsStep` to index the products in Algolia. You pass it the products you retrieved in the previous step.
|
||||
2. Use [transform](https://docs.medusajs.com/docs/learn/fundamentals/workflows/variable-manipulation/index.html.md) to prepare two lists: one for published products to index in Algolia and another for unpublished products to delete from Algolia.
|
||||
3. Execute `syncProductsStep` to index the published products in Algolia.
|
||||
4. Execute `deleteProductsFromAlgoliaStep` to delete unpublished products from Algolia.
|
||||
|
||||
A workflow must return an instance of `WorkflowResponse`. The `WorkflowResponse` constructor accepts the workflow's output as a parameter, which is an object holding the retrieved products and their pagination details.
|
||||
|
||||
@@ -94613,7 +94695,6 @@ export default async function handleProductEvents({
|
||||
input: {
|
||||
filters: {
|
||||
id: data.id,
|
||||
status: "published",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -94644,67 +94725,11 @@ Then, either create a product or update an existing one using the Medusa Admin d
|
||||
|
||||
When a product is deleted, you need to remove it from the Algolia index. As this requires a different action than creating or updating a product, you'll create a new workflow that deletes the product from Algolia, then create a subscriber that listens to the `product.deleted` event to trigger the workflow.
|
||||
|
||||
#### Create Delete Product Step
|
||||
|
||||
The workflow to delete a product from Algolia will have only one step that deletes products by their IDs from Algolia.
|
||||
|
||||
So, create the step at `src/workflows/steps/delete-products-from-algolia.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/steps/delete-products-from-algolia.ts"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { ALGOLIA_MODULE } from "../../modules/algolia"
|
||||
|
||||
export type DeleteProductsFromAlgoliaWorkflow = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteProductsFromAlgoliaStep = createStep(
|
||||
"delete-products-from-algolia-step",
|
||||
async (
|
||||
{ ids }: DeleteProductsFromAlgoliaWorkflow,
|
||||
{ container }
|
||||
) => {
|
||||
const algoliaModuleService = container.resolve(ALGOLIA_MODULE)
|
||||
|
||||
const existingRecords = await algoliaModuleService.retrieveFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
await algoliaModuleService.deleteFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
|
||||
return new StepResponse(undefined, existingRecords)
|
||||
},
|
||||
async (existingRecords, { container }) => {
|
||||
if (!existingRecords) {
|
||||
return
|
||||
}
|
||||
const algoliaModuleService = container.resolve(ALGOLIA_MODULE)
|
||||
|
||||
await algoliaModuleService.indexData(
|
||||
existingRecords as unknown as Record<string, unknown>[],
|
||||
"product"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The step receives the IDs of the products to delete as an input.
|
||||
|
||||
In the step, you resolve the Algolia Module's service and retrieve the existing records from Algolia. This is useful to revert the deletion if an error occurs.
|
||||
|
||||
Then, you delete the products from Algolia and pass the existing records to the compensation function.
|
||||
|
||||
In the compensation function, you reindex the existing records if an error occurs.
|
||||
|
||||
#### Create Delete Product Workflow
|
||||
|
||||
You can now create the workflow that deletes products from Algolia. Create the file `src/workflows/delete-products-from-algolia.ts` with the following content:
|
||||
You've already created the `deleteProductsFromAlgoliaStep` that deletes products from Algolia by their IDs. So, you can create the workflow that uses this step.
|
||||
|
||||
Create the file `src/workflows/delete-products-from-algolia.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/delete-products-from-algolia.ts"
|
||||
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
|
||||
@@ -101999,9 +102024,10 @@ In this step, you'll create a workflow that indexes products in Meilisearch. In
|
||||
The workflow has the following steps:
|
||||
|
||||
- [useQueryGraphStep](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve products matching specified filters and pagination parameters.
|
||||
- [syncProductsStep](#syncProductsStep): Index products in Meilisearch.
|
||||
- [syncProductsStep](#syncProductsStep): Index published products in Meilisearch.
|
||||
- [deleteProductsFromMeilisearchStep](#deleteProductsFromMeilisearchStep): Delete unpublished products from Meilisearch.
|
||||
|
||||
Medusa provides the `useQueryGraphStep` in its `@medusajs/medusa/core-flows` package. You only need to implement the second step.
|
||||
Medusa provides the `useQueryGraphStep` in its `@medusajs/medusa/core-flows` package. You only need to implement the other steps.
|
||||
|
||||
### syncProductsStep
|
||||
|
||||
@@ -102122,6 +102148,64 @@ The compensation function receives two parameters:
|
||||
|
||||
In the compensation function, you resolve the Meilisearch Module's service from the container. Then, you delete from Meilisearch the products that were newly indexed and revert the existing products to their original data.
|
||||
|
||||
#### deleteProductsFromMeilisearchStep
|
||||
|
||||
The `deleteProductsFromMeilisearchStep` step deletes products from Meilisearch when they are unpublished or deleted in Medusa.
|
||||
|
||||
Create the step at `src/workflows/steps/delete-products-from-meilisearch.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/steps/delete-products-from-meilisearch.ts"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { MEILISEARCH_MODULE } from "../../modules/meilisearch"
|
||||
|
||||
export type DeleteProductsFromMeilisearchStep = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteProductsFromMeilisearchStep = createStep(
|
||||
"delete-products-from-meilisearch-step",
|
||||
async (
|
||||
{ ids }: DeleteProductsFromMeilisearchStep,
|
||||
{ container }
|
||||
) => {
|
||||
const meilisearchModuleService = container.resolve(MEILISEARCH_MODULE)
|
||||
|
||||
const existingRecords = await meilisearchModuleService.retrieveFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
await meilisearchModuleService.deleteFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
|
||||
return new StepResponse(undefined, existingRecords)
|
||||
},
|
||||
async (existingRecords, { container }) => {
|
||||
if (!existingRecords) {
|
||||
return
|
||||
}
|
||||
const meilisearchModuleService = container.resolve(MEILISEARCH_MODULE)
|
||||
|
||||
await meilisearchModuleService.indexData(
|
||||
existingRecords,
|
||||
"product"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The step receives the IDs of the products to delete as an input.
|
||||
|
||||
In the step, you resolve the Meilisearch Module's service and retrieve the existing records from Meilisearch. This is useful to revert the deletion if an error occurs.
|
||||
|
||||
Then, you delete the products from Meilisearch and pass the existing records to the compensation function.
|
||||
|
||||
In the compensation function, you reindex the existing records if an error occurs.
|
||||
|
||||
### Add Sync Products Workflow
|
||||
|
||||
You can now create the workflow that syncs products to Meilisearch.
|
||||
@@ -102136,7 +102220,7 @@ import {
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
import { syncProductsStep, SyncProductsStepInput } from "./steps/sync-products"
|
||||
import { ProductStatus } from "@medusajs/framework/utils"
|
||||
import { deleteProductsFromMeilisearchStep } from "./steps/delete-products-from-meilisearch"
|
||||
|
||||
type SyncProductsWorkflowInput = {
|
||||
filters?: Record<string, unknown>
|
||||
@@ -102147,15 +102231,7 @@ type SyncProductsWorkflowInput = {
|
||||
export const syncProductsWorkflow = createWorkflow(
|
||||
"sync-products",
|
||||
({ filters, limit, offset }: SyncProductsWorkflowInput) => {
|
||||
const productFilters = transform({
|
||||
filters,
|
||||
}, (data) => {
|
||||
return {
|
||||
status: ProductStatus.PUBLISHED,
|
||||
...data.filters,
|
||||
}
|
||||
})
|
||||
const { data, metadata } = useQueryGraphStep({
|
||||
const { data: products, metadata } = useQueryGraphStep({
|
||||
entity: "product",
|
||||
fields: [
|
||||
"id",
|
||||
@@ -102168,21 +102244,49 @@ export const syncProductsWorkflow = createWorkflow(
|
||||
"categories.handle",
|
||||
"tags.id",
|
||||
"tags.value",
|
||||
"status",
|
||||
],
|
||||
pagination: {
|
||||
take: limit,
|
||||
skip: offset,
|
||||
},
|
||||
filters: productFilters,
|
||||
filters,
|
||||
})
|
||||
|
||||
const {
|
||||
publishedProducts,
|
||||
unpublishedProductsToDelete,
|
||||
} = transform({
|
||||
products,
|
||||
}, (data) => {
|
||||
const publishedProducts: SyncProductsStepInput["products"] = []
|
||||
const unpublishedProductsToDelete: string[] = []
|
||||
|
||||
data.products.forEach((product) => {
|
||||
if (product.status === "published") {
|
||||
const { status, ...rest } = product
|
||||
publishedProducts.push(rest as SyncProductsStepInput["products"][0])
|
||||
} else {
|
||||
unpublishedProductsToDelete.push(product.id)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
publishedProducts,
|
||||
unpublishedProductsToDelete,
|
||||
}
|
||||
})
|
||||
|
||||
syncProductsStep({
|
||||
products: data,
|
||||
} as SyncProductsStepInput)
|
||||
products: publishedProducts,
|
||||
})
|
||||
|
||||
deleteProductsFromMeilisearchStep({
|
||||
ids: unpublishedProductsToDelete,
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
return new WorkflowResponse({
|
||||
products: data,
|
||||
products,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
@@ -102196,12 +102300,16 @@ It accepts as a second parameter a constructor function, which is the workflow's
|
||||
In the workflow's constructor function, you:
|
||||
|
||||
1. Retrieve products from Medusa's database using `useQueryGraphStep`. This step uses Medusa's [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) tool to retrieve data across modules. You pass it the pagination and filter parameters you received in the input.
|
||||
2. Index the products in Meilisearch using `syncProductsStep`. You pass it the products you retrieved in the previous step.
|
||||
2. Use [transform](https://docs.medusajs.com/docs/learn/fundamentals/workflows/variable-manipulation/index.html.md) to prepare two lists: one for published products to index in Meilisearch and another for unpublished products to delete from Meilisearch.
|
||||
3. Index the published products in Meilisearch using `syncProductsStep`.
|
||||
4. Delete unpublished products from Meilisearch using `deleteProductsFromMeilisearchStep`.
|
||||
|
||||
A workflow must return an instance of `WorkflowResponse`. The `WorkflowResponse` constructor accepts the workflow's output as a parameter, which is an object holding the retrieved products and their pagination details.
|
||||
|
||||
In the next step, you'll learn how to execute this workflow.
|
||||
|
||||
In workflows, you need `transform` to prepare data based on execution values. Learn more in the [Data Manipulation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/variable-manipulation/index.html.md) workflow documentation.
|
||||
|
||||
***
|
||||
|
||||
## Step 4: Trigger Meilisearch Sync Manually
|
||||
@@ -102482,7 +102590,6 @@ export default async function handleProductEvents({
|
||||
input: {
|
||||
filters: {
|
||||
id: data.id,
|
||||
status: "published",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -102511,69 +102618,15 @@ Then, either create a product or update an existing one using the Medusa Admin d
|
||||
|
||||
### Handle Product Deletion
|
||||
|
||||
When a product is deleted, you need to remove it from the Meilisearch index. This requires a different action than creating or updating a product. You'll create a new workflow that deletes the product from Meilisearch, then create a subscriber that listens to the `product.deleted` event to trigger the workflow.
|
||||
When a product is deleted, you need to remove it from the Meilisearch index. This requires a different action than creating or updating a product.
|
||||
|
||||
#### Create Delete Product Step
|
||||
|
||||
The workflow to delete a product from Meilisearch will have only one step that deletes products by their IDs from Meilisearch.
|
||||
|
||||
So, create the step at `src/workflows/steps/delete-products-from-meilisearch.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/steps/delete-products-from-meilisearch.ts"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { MEILISEARCH_MODULE } from "../../modules/meilisearch"
|
||||
|
||||
export type DeleteProductsFromMeilisearchStep = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteProductsFromMeilisearchStep = createStep(
|
||||
"delete-products-from-meilisearch-step",
|
||||
async (
|
||||
{ ids }: DeleteProductsFromMeilisearchStep,
|
||||
{ container }
|
||||
) => {
|
||||
const meilisearchModuleService = container.resolve(MEILISEARCH_MODULE)
|
||||
|
||||
const existingRecords = await meilisearchModuleService.retrieveFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
await meilisearchModuleService.deleteFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
|
||||
return new StepResponse(undefined, existingRecords)
|
||||
},
|
||||
async (existingRecords, { container }) => {
|
||||
if (!existingRecords) {
|
||||
return
|
||||
}
|
||||
const meilisearchModuleService = container.resolve(MEILISEARCH_MODULE)
|
||||
|
||||
await meilisearchModuleService.indexData(
|
||||
existingRecords,
|
||||
"product"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The step receives the IDs of the products to delete as an input.
|
||||
|
||||
In the step, you resolve the Meilisearch Module's service and retrieve the existing records from Meilisearch. This is useful to revert the deletion if an error occurs.
|
||||
|
||||
Then, you delete the products from Meilisearch and pass the existing records to the compensation function.
|
||||
|
||||
In the compensation function, you reindex the existing records if an error occurs.
|
||||
You'll create a new workflow that deletes the product from Meilisearch, then create a subscriber that listens to the `product.deleted` event to trigger the workflow.
|
||||
|
||||
#### Create Delete Product Workflow
|
||||
|
||||
You can now create the workflow that deletes products from Meilisearch. Create the file `src/workflows/delete-products-from-meilisearch.ts` with the following content:
|
||||
Since you've already implemented the `deleteProductsFromMeilisearchStep`, you can create the workflow that uses this step.
|
||||
|
||||
Create the file `src/workflows/delete-products-from-meilisearch.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/delete-products-from-meilisearch.ts"
|
||||
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
|
||||
@@ -102595,7 +102648,7 @@ The workflow receives an object with the IDs of the products to delete. It then
|
||||
|
||||
#### Create Delete Product Subscriber
|
||||
|
||||
Finally, you'll create the subscriber that listens to the `product.deleted` event to trigger the above workflow.
|
||||
Next, you'll create the subscriber that listens to the `product.deleted` event to trigger the above workflow.
|
||||
|
||||
Create the file `src/subscribers/product-delete.ts` with the following content:
|
||||
|
||||
|
||||
@@ -403,15 +403,21 @@ The workflow has the following steps:
|
||||
{
|
||||
type: "step",
|
||||
name: "syncProductsStep",
|
||||
description: "Index products in Algolia.",
|
||||
depth: 1
|
||||
description: "Index published products in Algolia.",
|
||||
depth: 2
|
||||
},
|
||||
{
|
||||
type: "step",
|
||||
name: "deleteProductsFromAlgoliaStep",
|
||||
description: "Delete unpublished products from Algolia.",
|
||||
depth: 3
|
||||
}
|
||||
]
|
||||
}}
|
||||
hideLegend
|
||||
/>
|
||||
|
||||
Medusa provides the `useQueryGraphStep` in its `@medusajs/medusa/core-flows` package. So, you only need to implement the second step.
|
||||
Medusa provides the `useQueryGraphStep` in its `@medusajs/medusa/core-flows` package. So, you only need to implement the other steps.
|
||||
|
||||
### syncProductsStep
|
||||
|
||||
@@ -533,6 +539,64 @@ The compensation function receives two parameters:
|
||||
|
||||
In the compensation function, you resolve the Algolia Module's service from the container. Then, you delete from Algolia the products that were newly indexed, and revert the existing products to their original data.
|
||||
|
||||
#### deleteProductsFromAlgoliaStep
|
||||
|
||||
The `deleteProductsFromAlgoliaStep` step deletes products from Algolia by their IDs.
|
||||
|
||||
Create the step at `src/workflows/steps/delete-products-from-algolia.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/steps/delete-products-from-algolia.ts"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { ALGOLIA_MODULE } from "../../modules/algolia"
|
||||
|
||||
export type DeleteProductsFromAlgoliaWorkflow = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteProductsFromAlgoliaStep = createStep(
|
||||
"delete-products-from-algolia-step",
|
||||
async (
|
||||
{ ids }: DeleteProductsFromAlgoliaWorkflow,
|
||||
{ container }
|
||||
) => {
|
||||
const algoliaModuleService = container.resolve(ALGOLIA_MODULE)
|
||||
|
||||
const existingRecords = await algoliaModuleService.retrieveFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
await algoliaModuleService.deleteFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
|
||||
return new StepResponse(undefined, existingRecords)
|
||||
},
|
||||
async (existingRecords, { container }) => {
|
||||
if (!existingRecords) {
|
||||
return
|
||||
}
|
||||
const algoliaModuleService = container.resolve(ALGOLIA_MODULE)
|
||||
|
||||
await algoliaModuleService.indexData(
|
||||
existingRecords as unknown as Record<string, unknown>[],
|
||||
"product"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The step receives the IDs of the products to delete as an input.
|
||||
|
||||
In the step, you resolve the Algolia Module's service and retrieve the existing records from Algolia. This is useful to revert the deletion if an error occurs.
|
||||
|
||||
Then, you delete the products from Algolia and pass the existing records to the compensation function.
|
||||
|
||||
In the compensation function, you reindex the existing records if an error occurs.
|
||||
|
||||
### Add Sync Products Workflow
|
||||
|
||||
You can now create the workflow that syncs the products to Algolia.
|
||||
@@ -547,7 +611,7 @@ import {
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
import { syncProductsStep, SyncProductsStepInput } from "./steps/sync-products"
|
||||
import { ProductStatus } from "@medusajs/framework/utils"
|
||||
import { deleteProductsFromAlgoliaStep } from "./steps/delete-products-from-algolia"
|
||||
|
||||
type SyncProductsWorkflowInput = {
|
||||
filters?: Record<string, unknown>
|
||||
@@ -558,15 +622,7 @@ type SyncProductsWorkflowInput = {
|
||||
export const syncProductsWorkflow = createWorkflow(
|
||||
"sync-products",
|
||||
({ filters, limit, offset }: SyncProductsWorkflowInput) => {
|
||||
const productFilters = transform({
|
||||
filters,
|
||||
}, (data) => {
|
||||
return {
|
||||
status: ProductStatus.PUBLISHED,
|
||||
...data.filters,
|
||||
}
|
||||
})
|
||||
const { data, metadata } = useQueryGraphStep({
|
||||
const { data: products, metadata } = useQueryGraphStep({
|
||||
entity: "product",
|
||||
fields: [
|
||||
"id",
|
||||
@@ -579,20 +635,49 @@ export const syncProductsWorkflow = createWorkflow(
|
||||
"categories.handle",
|
||||
"tags.id",
|
||||
"tags.value",
|
||||
"status",
|
||||
],
|
||||
pagination: {
|
||||
take: limit,
|
||||
skip: offset,
|
||||
},
|
||||
filters: productFilters,
|
||||
filters,
|
||||
})
|
||||
|
||||
const {
|
||||
publishedProducts,
|
||||
unpublishedProductsToDelete,
|
||||
} = transform({
|
||||
products,
|
||||
}, (data) => {
|
||||
const publishedProducts: SyncProductsStepInput["products"] = []
|
||||
const unpublishedProductsToDelete: string[] = []
|
||||
|
||||
data.products.forEach((product) => {
|
||||
if (product.status === "published") {
|
||||
const { status, ...rest } = product
|
||||
publishedProducts.push(rest as SyncProductsStepInput["products"][0])
|
||||
} else {
|
||||
unpublishedProductsToDelete.push(product.id)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
publishedProducts,
|
||||
unpublishedProductsToDelete,
|
||||
}
|
||||
})
|
||||
|
||||
syncProductsStep({
|
||||
products: data,
|
||||
} as SyncProductsStepInput)
|
||||
products: publishedProducts,
|
||||
})
|
||||
|
||||
deleteProductsFromAlgoliaStep({
|
||||
ids: unpublishedProductsToDelete,
|
||||
})
|
||||
|
||||
return new WorkflowResponse({
|
||||
products: data,
|
||||
products,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
@@ -606,7 +691,9 @@ It accepts as a second parameter a constructor function, which is the workflow's
|
||||
In the workflow's constructor function, you:
|
||||
|
||||
1. Execute `useQueryGraphStep` to retrieve products from Medusa's database. This step uses Medusa's [Query](!docs!/learn/fundamentals/module-links/query) tool to retrieve data across modules. You pass it the pagination and filter parameters you received in the input.
|
||||
2. Execute `syncProductsStep` to index the products in Algolia. You pass it the products you retrieved in the previous step.
|
||||
2. Use [transform](!docs!/learn/fundamentals/workflows/variable-manipulation) to prepare two lists: one for published products to index in Algolia and another for unpublished products to delete from Algolia.
|
||||
2. Execute `syncProductsStep` to index the published products in Algolia.
|
||||
3. Execute `deleteProductsFromAlgoliaStep` to delete unpublished products from Algolia.
|
||||
|
||||
A workflow must return an instance of `WorkflowResponse`. The `WorkflowResponse` constructor accepts the workflow's output as a parameter, which is an object holding the retrieved products and their pagination details.
|
||||
|
||||
@@ -908,7 +995,6 @@ export default async function handleProductEvents({
|
||||
input: {
|
||||
filters: {
|
||||
id: data.id,
|
||||
status: "published",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -939,67 +1025,11 @@ Then, either create a product or update an existing one using the Medusa Admin d
|
||||
|
||||
When a product is deleted, you need to remove it from the Algolia index. As this requires a different action than creating or updating a product, you'll create a new workflow that deletes the product from Algolia, then create a subscriber that listens to the `product.deleted` event to trigger the workflow.
|
||||
|
||||
#### Create Delete Product Step
|
||||
|
||||
The workflow to delete a product from Algolia will have only one step that deletes products by their IDs from Algolia.
|
||||
|
||||
So, create the step at `src/workflows/steps/delete-products-from-algolia.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/steps/delete-products-from-algolia.ts"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { ALGOLIA_MODULE } from "../../modules/algolia"
|
||||
|
||||
export type DeleteProductsFromAlgoliaWorkflow = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteProductsFromAlgoliaStep = createStep(
|
||||
"delete-products-from-algolia-step",
|
||||
async (
|
||||
{ ids }: DeleteProductsFromAlgoliaWorkflow,
|
||||
{ container }
|
||||
) => {
|
||||
const algoliaModuleService = container.resolve(ALGOLIA_MODULE)
|
||||
|
||||
const existingRecords = await algoliaModuleService.retrieveFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
await algoliaModuleService.deleteFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
|
||||
return new StepResponse(undefined, existingRecords)
|
||||
},
|
||||
async (existingRecords, { container }) => {
|
||||
if (!existingRecords) {
|
||||
return
|
||||
}
|
||||
const algoliaModuleService = container.resolve(ALGOLIA_MODULE)
|
||||
|
||||
await algoliaModuleService.indexData(
|
||||
existingRecords as unknown as Record<string, unknown>[],
|
||||
"product"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The step receives the IDs of the products to delete as an input.
|
||||
|
||||
In the step, you resolve the Algolia Module's service and retrieve the existing records from Algolia. This is useful to revert the deletion if an error occurs.
|
||||
|
||||
Then, you delete the products from Algolia and pass the existing records to the compensation function.
|
||||
|
||||
In the compensation function, you reindex the existing records if an error occurs.
|
||||
|
||||
#### Create Delete Product Workflow
|
||||
|
||||
You can now create the workflow that deletes products from Algolia. Create the file `src/workflows/delete-products-from-algolia.ts` with the following content:
|
||||
You've already created the `deleteProductsFromAlgoliaStep` that deletes products from Algolia by their IDs. So, you can create the workflow that uses this step.
|
||||
|
||||
Create the file `src/workflows/delete-products-from-algolia.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/delete-products-from-algolia.ts"
|
||||
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
@@ -380,15 +380,21 @@ The workflow has the following steps:
|
||||
{
|
||||
type: "step",
|
||||
name: "syncProductsStep",
|
||||
description: "Index products in Meilisearch.",
|
||||
depth: 1
|
||||
description: "Index published products in Meilisearch.",
|
||||
depth: 2
|
||||
},
|
||||
{
|
||||
type: "step",
|
||||
name: "deleteProductsFromMeilisearchStep",
|
||||
description: "Delete unpublished products from Meilisearch.",
|
||||
depth: 3
|
||||
}
|
||||
]
|
||||
}}
|
||||
hideLegend
|
||||
/>
|
||||
|
||||
Medusa provides the `useQueryGraphStep` in its `@medusajs/medusa/core-flows` package. You only need to implement the second step.
|
||||
Medusa provides the `useQueryGraphStep` in its `@medusajs/medusa/core-flows` package. You only need to implement the other steps.
|
||||
|
||||
### syncProductsStep
|
||||
|
||||
@@ -513,6 +519,64 @@ The compensation function receives two parameters:
|
||||
|
||||
In the compensation function, you resolve the Meilisearch Module's service from the container. Then, you delete from Meilisearch the products that were newly indexed and revert the existing products to their original data.
|
||||
|
||||
#### deleteProductsFromMeilisearchStep
|
||||
|
||||
The `deleteProductsFromMeilisearchStep` step deletes products from Meilisearch when they are unpublished or deleted in Medusa.
|
||||
|
||||
Create the step at `src/workflows/steps/delete-products-from-meilisearch.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/steps/delete-products-from-meilisearch.ts"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { MEILISEARCH_MODULE } from "../../modules/meilisearch"
|
||||
|
||||
export type DeleteProductsFromMeilisearchStep = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteProductsFromMeilisearchStep = createStep(
|
||||
"delete-products-from-meilisearch-step",
|
||||
async (
|
||||
{ ids }: DeleteProductsFromMeilisearchStep,
|
||||
{ container }
|
||||
) => {
|
||||
const meilisearchModuleService = container.resolve(MEILISEARCH_MODULE)
|
||||
|
||||
const existingRecords = await meilisearchModuleService.retrieveFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
await meilisearchModuleService.deleteFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
|
||||
return new StepResponse(undefined, existingRecords)
|
||||
},
|
||||
async (existingRecords, { container }) => {
|
||||
if (!existingRecords) {
|
||||
return
|
||||
}
|
||||
const meilisearchModuleService = container.resolve(MEILISEARCH_MODULE)
|
||||
|
||||
await meilisearchModuleService.indexData(
|
||||
existingRecords,
|
||||
"product"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The step receives the IDs of the products to delete as an input.
|
||||
|
||||
In the step, you resolve the Meilisearch Module's service and retrieve the existing records from Meilisearch. This is useful to revert the deletion if an error occurs.
|
||||
|
||||
Then, you delete the products from Meilisearch and pass the existing records to the compensation function.
|
||||
|
||||
In the compensation function, you reindex the existing records if an error occurs.
|
||||
|
||||
### Add Sync Products Workflow
|
||||
|
||||
You can now create the workflow that syncs products to Meilisearch.
|
||||
@@ -527,7 +591,7 @@ import {
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
import { syncProductsStep, SyncProductsStepInput } from "./steps/sync-products"
|
||||
import { ProductStatus } from "@medusajs/framework/utils"
|
||||
import { deleteProductsFromMeilisearchStep } from "./steps/delete-products-from-meilisearch"
|
||||
|
||||
type SyncProductsWorkflowInput = {
|
||||
filters?: Record<string, unknown>
|
||||
@@ -538,15 +602,7 @@ type SyncProductsWorkflowInput = {
|
||||
export const syncProductsWorkflow = createWorkflow(
|
||||
"sync-products",
|
||||
({ filters, limit, offset }: SyncProductsWorkflowInput) => {
|
||||
const productFilters = transform({
|
||||
filters,
|
||||
}, (data) => {
|
||||
return {
|
||||
status: ProductStatus.PUBLISHED,
|
||||
...data.filters,
|
||||
}
|
||||
})
|
||||
const { data, metadata } = useQueryGraphStep({
|
||||
const { data: products, metadata } = useQueryGraphStep({
|
||||
entity: "product",
|
||||
fields: [
|
||||
"id",
|
||||
@@ -559,21 +615,49 @@ export const syncProductsWorkflow = createWorkflow(
|
||||
"categories.handle",
|
||||
"tags.id",
|
||||
"tags.value",
|
||||
"status",
|
||||
],
|
||||
pagination: {
|
||||
take: limit,
|
||||
skip: offset,
|
||||
},
|
||||
filters: productFilters,
|
||||
filters,
|
||||
})
|
||||
|
||||
const {
|
||||
publishedProducts,
|
||||
unpublishedProductsToDelete,
|
||||
} = transform({
|
||||
products,
|
||||
}, (data) => {
|
||||
const publishedProducts: SyncProductsStepInput["products"] = []
|
||||
const unpublishedProductsToDelete: string[] = []
|
||||
|
||||
data.products.forEach((product) => {
|
||||
if (product.status === "published") {
|
||||
const { status, ...rest } = product
|
||||
publishedProducts.push(rest as SyncProductsStepInput["products"][0])
|
||||
} else {
|
||||
unpublishedProductsToDelete.push(product.id)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
publishedProducts,
|
||||
unpublishedProductsToDelete,
|
||||
}
|
||||
})
|
||||
|
||||
syncProductsStep({
|
||||
products: data,
|
||||
} as SyncProductsStepInput)
|
||||
products: publishedProducts,
|
||||
})
|
||||
|
||||
deleteProductsFromMeilisearchStep({
|
||||
ids: unpublishedProductsToDelete,
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
return new WorkflowResponse({
|
||||
products: data,
|
||||
products,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
@@ -587,12 +671,20 @@ It accepts as a second parameter a constructor function, which is the workflow's
|
||||
In the workflow's constructor function, you:
|
||||
|
||||
1. Retrieve products from Medusa's database using `useQueryGraphStep`. This step uses Medusa's [Query](!docs!/learn/fundamentals/module-links/query) tool to retrieve data across modules. You pass it the pagination and filter parameters you received in the input.
|
||||
2. Index the products in Meilisearch using `syncProductsStep`. You pass it the products you retrieved in the previous step.
|
||||
2. Use [transform](!docs!/learn/fundamentals/workflows/variable-manipulation) to prepare two lists: one for published products to index in Meilisearch and another for unpublished products to delete from Meilisearch.
|
||||
3. Index the published products in Meilisearch using `syncProductsStep`.
|
||||
4. Delete unpublished products from Meilisearch using `deleteProductsFromMeilisearchStep`.
|
||||
|
||||
A workflow must return an instance of `WorkflowResponse`. The `WorkflowResponse` constructor accepts the workflow's output as a parameter, which is an object holding the retrieved products and their pagination details.
|
||||
|
||||
In the next step, you'll learn how to execute this workflow.
|
||||
|
||||
<Note>
|
||||
|
||||
In workflows, you need `transform` to prepare data based on execution values. Learn more in the [Data Manipulation](!docs!/learn/fundamentals/workflows/variable-manipulation) workflow documentation.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Trigger Meilisearch Sync Manually
|
||||
@@ -893,7 +985,6 @@ export default async function handleProductEvents({
|
||||
input: {
|
||||
filters: {
|
||||
id: data.id,
|
||||
status: "published",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -922,69 +1013,15 @@ Then, either create a product or update an existing one using the Medusa Admin d
|
||||
|
||||
### Handle Product Deletion
|
||||
|
||||
When a product is deleted, you need to remove it from the Meilisearch index. This requires a different action than creating or updating a product. You'll create a new workflow that deletes the product from Meilisearch, then create a subscriber that listens to the `product.deleted` event to trigger the workflow.
|
||||
When a product is deleted, you need to remove it from the Meilisearch index. This requires a different action than creating or updating a product.
|
||||
|
||||
#### Create Delete Product Step
|
||||
|
||||
The workflow to delete a product from Meilisearch will have only one step that deletes products by their IDs from Meilisearch.
|
||||
|
||||
So, create the step at `src/workflows/steps/delete-products-from-meilisearch.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/steps/delete-products-from-meilisearch.ts"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { MEILISEARCH_MODULE } from "../../modules/meilisearch"
|
||||
|
||||
export type DeleteProductsFromMeilisearchStep = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteProductsFromMeilisearchStep = createStep(
|
||||
"delete-products-from-meilisearch-step",
|
||||
async (
|
||||
{ ids }: DeleteProductsFromMeilisearchStep,
|
||||
{ container }
|
||||
) => {
|
||||
const meilisearchModuleService = container.resolve(MEILISEARCH_MODULE)
|
||||
|
||||
const existingRecords = await meilisearchModuleService.retrieveFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
await meilisearchModuleService.deleteFromIndex(
|
||||
ids,
|
||||
"product"
|
||||
)
|
||||
|
||||
return new StepResponse(undefined, existingRecords)
|
||||
},
|
||||
async (existingRecords, { container }) => {
|
||||
if (!existingRecords) {
|
||||
return
|
||||
}
|
||||
const meilisearchModuleService = container.resolve(MEILISEARCH_MODULE)
|
||||
|
||||
await meilisearchModuleService.indexData(
|
||||
existingRecords,
|
||||
"product"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The step receives the IDs of the products to delete as an input.
|
||||
|
||||
In the step, you resolve the Meilisearch Module's service and retrieve the existing records from Meilisearch. This is useful to revert the deletion if an error occurs.
|
||||
|
||||
Then, you delete the products from Meilisearch and pass the existing records to the compensation function.
|
||||
|
||||
In the compensation function, you reindex the existing records if an error occurs.
|
||||
You'll create a new workflow that deletes the product from Meilisearch, then create a subscriber that listens to the `product.deleted` event to trigger the workflow.
|
||||
|
||||
#### Create Delete Product Workflow
|
||||
|
||||
You can now create the workflow that deletes products from Meilisearch. Create the file `src/workflows/delete-products-from-meilisearch.ts` with the following content:
|
||||
Since you've already implemented the `deleteProductsFromMeilisearchStep`, you can create the workflow that uses this step.
|
||||
|
||||
Create the file `src/workflows/delete-products-from-meilisearch.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/delete-products-from-meilisearch.ts"
|
||||
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
|
||||
@@ -1006,7 +1043,7 @@ The workflow receives an object with the IDs of the products to delete. It then
|
||||
|
||||
#### Create Delete Product Subscriber
|
||||
|
||||
Finally, you'll create the subscriber that listens to the `product.deleted` event to trigger the above workflow.
|
||||
Next, you'll create the subscriber that listens to the `product.deleted` event to trigger the above workflow.
|
||||
|
||||
Create the file `src/subscribers/product-delete.ts` with the following content:
|
||||
|
||||
|
||||
@@ -6039,7 +6039,7 @@ export const generatedEditDates = {
|
||||
"app/troubleshooting/workflow-errors/step-x-defined/page.mdx": "2025-03-21T07:09:02.741Z",
|
||||
"app/troubleshooting/workflow-errors/when-then/page.mdx": "2025-03-21T08:35:45.145Z",
|
||||
"app/how-to-tutorials/tutorials/abandoned-cart/page.mdx": "2025-06-26T11:45:57.112Z",
|
||||
"app/integrations/guides/algolia/page.mdx": "2025-11-20T15:20:38.696Z",
|
||||
"app/integrations/guides/algolia/page.mdx": "2025-11-27T08:21:37.971Z",
|
||||
"app/integrations/guides/magento/page.mdx": "2025-10-09T11:30:09.533Z",
|
||||
"app/js-sdk/auth/overview/page.mdx": "2025-03-28T08:05:32.622Z",
|
||||
"app/how-to-tutorials/tutorials/loyalty-points/page.mdx": "2025-10-09T11:27:14.961Z",
|
||||
@@ -6600,7 +6600,7 @@ export const generatedEditDates = {
|
||||
"references/core_flows/Locking/Steps_Locking/variables/core_flows.Locking.Steps_Locking.acquireLockStepId/page.mdx": "2025-09-15T09:52:14.218Z",
|
||||
"references/core_flows/Locking/Steps_Locking/variables/core_flows.Locking.Steps_Locking.releaseLockStepId/page.mdx": "2025-09-15T09:52:14.219Z",
|
||||
"references/core_flows/Locking/core_flows.Locking.Steps_Locking/page.mdx": "2025-09-15T09:52:14.217Z",
|
||||
"app/integrations/guides/meilisearch/page.mdx": "2025-11-20T15:21:44.830Z",
|
||||
"app/integrations/guides/meilisearch/page.mdx": "2025-11-27T08:21:28.779Z",
|
||||
"app/nextjs-starter/guides/storefront-returns/page.mdx": "2025-09-22T06:02:00.580Z",
|
||||
"references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.views/page.mdx": "2025-10-31T09:41:41.343Z",
|
||||
"app/data-model-repository-reference/methods/create/page.mdx": "2025-10-28T16:02:14.959Z",
|
||||
|
||||
Reference in New Issue
Block a user