Search for a command to run...
The 1C exchange runs as Medusa workflows, and the plugin exposes hooks in several of them. A hook handler receives what the exchange is about to write to Medusa or send to 1C, and returns a corrected version. That's how you move 1C requisites into product fields, add data Medusa doesn't have to an order, or enable a price list for a customer group, without changing the plugin's code.
A workflow hook is a point inside a Medusa workflow where you plug in your own function. Medusa calls it while the workflow runs and passes it data, and the workflow uses whatever the function returns. Read more in the Medusa documentation.
Import the workflows from , and the input and result types of the hooks and the functions that read 1C requisites and properties from .
During the beta, hook names and element shapes may still change based on participants' feedback. After the release, they change only with a major version of the plugin.
Register the handler in a file under in your Medusa project. Medusa loads it when the server starts. For example, this handler keeps 1C services out of the store:
src/workflows/hooks/onec-catalog.ts1import { StepResponse } from "@medusajs/framework/workflows-sdk"2import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"3import { readRequisite } from "@gorgo-store/medusa-erp-1c/hooks"45importOnecProductsWorkflow.hooks.setProducts(async ({ products }) => {6 return new StepResponse({7 products: products.filter(({ source }) => readRequisite(source, "ВидНоменклатуры") !== "Услуга"),8 })9})
The handler receives the batch's products and returns them in a , keeping only those whose "ВидНоменклатуры" requisite isn't "Услуга" (service). reads a requisite of a 1C product by its name, so you don't parse CommerceML yourself.
Every hook except receives an array of elements. An element brings together everything the decision needs:
The handler edits the target field and returns the elements. works differently. It receives the finished XML as a string, and it's there for what the product or document model doesn't cover, for example the schema version your 1C configuration expects.
A handler that returns a plain object or nothing stops the exchange with an error like . The plugin only gets what's inside the from Medusa.
Medusa allows only one handler per hook in the whole application. A second handler registered on the same hook stops the server from starting with the error . Gather all of your project's changes for one hook into a single handler and run them one after another inside it.
The handler runs inside a workflow step, and 1C waits for a response while it runs. and run on every batch, so a 10,000-product catalog calls each of them a hundred times. The workflow engine copies the hook's input and result, and anything you add to an element is copied along with it.
Here's what three kinds of handler cost:
| Handler | Database Queries per Catalog Import | Catalog Re-Import Time |
|---|---|---|
| Only fields from the input | as many as without a handler | within run-to-run spread |
| One query per batch | +10 | within run-to-run spread |
| One query per product | +990 | +12% |
The measurement used a catalog of 1,000 products and 4,000 offers, which is 10 batches, with a local PostgreSQL where a query takes about a millisecond. The table shows medians of three runs, and one run for the query-per-product handler. A re-import barely writes anything, so a handler's cost shows most there. On a remote database every query also costs network latency, and the gap grows.
To keep a handler from slowing the exchange down:
This is what a query per product looks like. For a batch of 100 products, it's 100 queries:
1for (const item of products) {2 const [brand] = readProperty(item.source, properties, "Бренд")3 const { data } = await query.graph({4 entity: "product_collection",5 fields: ["id"],6 filters: { title: brand },7 })8 // ...9}
One query per batch gets the same for all products at once:
1const titles = products.flatMap(({ source }) => readProperty(source, properties, "Бренд"))2const { data } = await query.graph({3 entity: "product_collection",4 fields: ["id", "title"],5 filters: { title: titles },6})7const collectionIdByTitle = new Map(data.map((collection) => [collection.title, collection.id]))
The full example with this query is on the Hook Examples page.
On a test catalog of a few products, a query per product goes unnoticed. It shows up on the full catalog, once the exchange is already running in production.
Start the server with . The plugin logs the duration of every catalog import step, and the hook's step is named after the hook:
runs inside the child workflow of the offers import, so its time is part of the overall line. Exports aren't logged step by step.