github medusajs/medusa v2.8.0

latest releases: v2.10.1, @medusajs/workflows-sdk@2.10.1, @medusajs/workflow-engine-redis@2.10.1...
3 months ago

Highlights

Important: This release introduced a regression with product updates

We unintentionally introduced a regression that affected the ordering of products returned from the product module service. This broke workflows (e.g. updateProductsWorkflow) that rely on consistent ordering for subsequent operations such as applying price updates.

Upgrade to v2.8.1 as soon as possible to avoid any issues related to this change.

Cart-completion robustness

Warning

Breaking changes

This release introduces important changes to the completeCartWorkflow aimed at improving robustness and making it easier to recover from failures.

Changes to idempotency

Until now, the workflow has had the following configuration:

{
  name: completeCartWorkflowId
  store: true,
  idempotent: true,
  retentionTime: THREE_DAYS,
}

Here's a brief description of what these properties do, excluding name:

  • store: Store the workflow execution. If no retention time is specified, the workflow is only stored for the duration of its execution
  • idempotent: Store the result of the first workflow execution and return it on subsequent runs
  • retentionTime: Store the result in the configured data store for "x" amount of time–here, three days

In this release, we change the idempotent flag from true to false. Initially, the idea of having an idempotent cart-completion stemmed from wanting to ensure that multiple requests to complete the same cart would lead to the same outcome. This is, in of itself, still reasonable to ensure there is only ever one attempt to complete the same cart. However, in case of failures, the idempotent mechanism makes it challenging to recover, since a new cart with the same details is required to re-attempt the completion. This is because the result of the failing workflow is stored and returned on all subsequent runs, regardless of whether the cart is updated to satisfy the constraints for completion.

With idempotent set to false, it will be possible to retry failed executions, making it simpler to bring the cart to a satisfying state and eventually create the order.

It is worth mentioning that concurrency continues to be appropriately managed by passing the cart ID as the transactionId when running the workflow. The transaction ID ensures that only one workflow execution can run at a time.

Changes to the structure

The workflow has been slightly restructured to minimize the risk of payment sessions getting stuck in a state that is difficult to recover from. More specifically, the payment authorization step has been moved to the very end of the workflow, so that all first-party checks are performed before we authorize the payment.

To understand why this is important, we need to cover the compensateIfNeeded step of the workflow briefly. This step compensates active payment sessions in the case of failure to avoid orphaned third-party payments and recreates the Medusa payment session so that new payment details can be attached.

For example, suppose the payment authorization is performed off-band (e.g., in a webhook), and the cart-completion workflow fails to execute. In that case, the step will void the third-party payment and bring the cart to a correct state, all while ensuring consistency between the two systems.

Moving the payment authorization step to the end of the workflow ensures that we only create an authorized payment if all other cart constraints are satisfied.

Changes to the webhook handler

The webhook handler ensures consistency between Medusa and the third-party payment provider. More specifically, it will handle the following scenarios:

  • Payment authorization: when we receive a payment authorization event, we will attempt to authorize and create a payment in Medusa
  • Payment capture: when we receive a payment capture event, we will attempt to capture the associated payment in Medusa
  • Cart-completion: when we receive a payment authorization event, we will attempt to complete the cart
  • Payment authorization and capture: when we receive a payment capture event and there is no existing payment in Medusa, we will attempt to authorize, create, and capture a payment in Medusa

We have only handled the first three scenarios until now. This release introduces functionality for the fourth, a more unusual scenario involving auto-captures in the third-party payment provider.

Line item generation

Warning

Breaking changes

This release changes the source of line item titles and subtitles. The title has changed from the variant title to the product title, and the subtitle has changed from the product title to the variant title.

Cross-module filters (experimental)

This release introduces experimental support for cross-module filters via a new module, @medusajs/index.

Please remember that this module is experimental and in development, so please use it cautiously. It is subject to change as we finalize the design to enable cross-module filtering.

We will keep the description brief since the module is still in development. Here's what you need to know:

  • Medusa's architecture is divided into a range of modules, each being isolated (at the data model level) from one another. By default, the modules share the same database, but the architecture is built to support external data sources, in case your module needs a different type of database, or you want to use a third-party service in place of Medusa's core module
  • Consequently, queries to retrieve data are performed per module, and the data is aggregated in the end to create the result set
  • Our new index module allows you to ingest data into a central, relational-like structure
  • The structure enables efficient filtering over data coming from different data sources (provided it is ingested)

The index module can be enabled by installing the module:

yarn add @medusajs/index

Adding it to your Medusa config:

{
  // ...rest of config
  modules: [{ resolve: "@medusajs/index", options: {} }]
}

And finally, adding a feature flag to enable it:

// .env
MEDUSA_FF_INDEX_ENGINE=true

Then, apply the migrations to create the related tables:

npx medusa db:migrate

The next time you start your server, we will ingest data into the index engine.

In this first iteration, we only ingest the following core entities:

  • Products
  • Product Variants
  • Prices
  • Sales Channels

The data in the index is queried through a new API with semantics identical to the existing query graph:

  const { data: products = [], metadata } = await query.index({
    entity: "product",
    fields: ["id", "name"],
  });

In addition to ingesting a few select resources from the core, we have added support for ingesting data from custom modules through link definitions. For example, say you want to introduce a Brand on Products and filter your Products by it.

Assuming you have added a Brand module, you create a link as follows:

import { defineLink } from "@medusajs/framework/utils";
import ProductModule from "@medusajs/medusa/product";
import BrandModule from "../modules/brand";

export default defineLink(
  {
    linkable: ProductModule.linkable.product,
    isList: true,
  },
  {
    linkable: BrandModule.linkable.brand,
  }
);

This link will associate the two data models and allow you to query them from both sides of the link.

Furthermore, to ingest the Brand into the index and make the data filterable, you need to add the new filterable property to the link:

import { defineLink } from "@medusajs/framework/utils";
import ProductModule from "@medusajs/medusa/product";
import BrandModule from "../modules/brand";

export default defineLink(
  {
    linkable: ProductModule.linkable.product,
    isList: true,
  },
  {
    linkable: BrandModule.linkable.brand,
    filterable: ["id", "name"]
  }
);

The link above specifies that Brands should be ingested into the index and made filterable via the properties id and title.

As a result, you can now filter Products by Brands ID or title like so:

  const { data: products = [], metadata } = await query.index({
    entity: "product",
    fields: ["id", "title", "brand.*"],
    filters: {
      brand: {
        name: "Hermes"
      }
    },
  });

An important trade-off in the index module is the need to avoid the performance cost of COUNT(*) queries, which can be an incredibly high cost for very large data sets. Instead, we use Postgres’ internal query planner to compute an estimated count of the result set. For very small data sets, this estimate can be very inaccurate, but for larger ones it should provide a reasonable estimate. The trade-off allows us to achieve very fast response times making it ideal for user-facing search and browsing experiences. As noted earlier, the tool is still experimental, and we plan to continue iterating and improving it over time.

Custom Tax Providers

This release introduces full support for custom Tax Providers by making these key changes:

  • Load custom Tax Providers in the Tax Module
  • Manage (create, update) provider per Tax Region in the admin dashboard
  • Migrate all Tax Regions to use a tax provider

The migration script will run the first time you start the server after upgrading. It will assign the default system provider to the Tax Region, if the region did not have a custom provider specified already.

Features

Bugs

  • fix(medusa): Fix store return API by @riqwan in #12284
  • fix(core-flows): handle inventory kit items in mark-as-shipped/delivered flows by @fPolic in #12269
  • fix(design-system): filter/sorting menu error by @fPolic in #12309
  • fix(core-flows): add missing remove remote link by @carlos-r-l-rodrigues in #12326
  • fix(core-flows,utils): move fulfillment workflow events by @shahednasser in #12338
  • fix: batch updates in upsertMany_ by @peterlgh7 in #12333
  • fix: export ProductImage to allow for custom links to add alt text by @thetutlage in #12357
  • fix(product): Return updated collections by @olivermrbl in #12354
  • fix(create-medusa-app): updates to text and prompts by @shahednasser in #12360
  • fix(medusa): Support additional data on admin/collections by @olivermrbl in #12345
  • fix(core-flows): export order-related utility workflows by @shahednasser in #12380
  • fix(medusa): check if tax module is installed when running tax migration script by @fPolic in #12393
  • fix: Use logger in the test runner and migration commands by @sradevski in #12406
  • fix: refactor batch product update by @peterlgh7 in #12367
  • fix(dashboard): tax regions UI polish by @fPolic in #12437
  • fix(core-flows): use product title for line item title by @fPolic in #12397
  • fix(dashboard): inventory kit combobox state by @fPolic in #12371
  • fix(core-flows): use transform as input to account holder step by @riqwan in #12430
  • fix(dashboard): call route modal onClose only on route change by @fPolic in #12383
  • fix(core-flows): Refund and recreate payment session on cart complete failure by @fPolic in #12263
  • fix(tax): Make system provider registration backward compatible by @olivermrbl in #12441
  • fix(dashboard, medusa): validate provider for top level tax regions by @fPolic in #12450
  • fix(core-flows, dashboard): reservation recreation on fulfilment cancel + allocation button display by @fPolic in #12447

Documentation

Chores

Other Changes

New Contributors

Full Changelog: v2.7.1...v2.8.0

Don't miss a new medusa release

NewReleases is sending notifications on new releases.