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 executionidempotent
: Store the result of the first workflow execution and return it on subsequent runsretentionTime
: 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
- feat: upgrade opentelemetry packages to support the latest release by @thetutlage in #12266
- feat(index): add filterable fields to link definition by @carlos-r-l-rodrigues in #11898
- feat: add needed methods to the file module and providers by @thetutlage in #12325
- feat(core-flows, utils): add OrderEdit events by @fPolic in #12320
- feat: implement direct upload by @thetutlage in #12328
- feat(dashboard, js-sdk, medusa, tax, types): custom tax providers by @fPolic in #12297
- feat(product): search variants by product properties by @fPolic in #12391
- fix(workflow-engine-*): q text search by @adrien2p in #12435
- feat(core-flows): Emit cart trasnferred customer by @adrien2p in #12425
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
- docs: fix reference of draft orders JS SDK methods by @shahednasser in #12271
- docs: fix core flows reference not generating by @shahednasser in #12274
- docs: regenerate core-flows reference + OAS for draft order updates by @shahednasser in #12280
- docs: clarify ISO format for province codes by @shahednasser in #12282
- docs: add note to s3 guide by @shahednasser in #12289
- docs: improve documetation around error handling in workflows by @shahednasser in #12288
- docs: added mark fulfillment delivered step in digital products recipe by @shahednasser in #12290
- docs-util: enable internal resolve for workflows sdk reference by @shahednasser in #12292
- docs: improve resend guide by @shahednasser in #12293
- Update 6.3. Using TypeScript Aliases - Alias should run after build, not before by @AlexanderLindkjaer in #12272
- docs: general improvements and additions by @shahednasser in #12296
- docs: fix troubleshooting page by @shahednasser in #12305
- docs-util: add sidebar link validation in prep script by @shahednasser in #12306
- docs: fix module name in product review guide by @shahednasser in #12310
- docs(algolia): rename instructed filename to fit code-block and preserve naming conflict instructions by @MentorFilou in #12300
- docs: improve installation documentation with diagrams by @shahednasser in #12312
- docs: fix incorrect note usage in some workflow references by @shahednasser in #12317
- docs: fix missing quotation mark in marketplace vendor tutorial curl request by @Obad94 in #12315
- docs: localization with contentful guide by @shahednasser in #12202
- docs-util: fix hook examples not showing in generated references by @shahednasser in #12334
- docs: fix TypeScript errors in marketplace vendor tutorial by @Obad94 in #12302
- docs-util: support deprecated tag in OAS by @shahednasser in #12339
- docs-util: add script to generate events info by @shahednasser in #12340
- docs: generate events reference by @shahednasser in #12341
- docs: fix redirect on infrastructural modules by @shahednasser in #12358
- docs: fix local links in UI docs by @shahednasser in #12359
- docs: add script to catch bad redirects by @shahednasser in #12361
- docs: add troubleshooting for index.html error by @shahednasser in #12365
- docs: add missing details for tiered pricing by @shahednasser in #12374
- docs-util: add events to workflows reference by @shahednasser in #12376
- docs: generate event references by @shahednasser in #12378
- docs-util: fix hook parameters not showing when using workflow input by @shahednasser in #12382
- docs: fixes to navbar and sidebar in mobile by @shahednasser in #12379
- docs: update import user guide text + add diagram in brand module guide by @shahednasser in #12385
- docs-util: add events to generated OAS by @shahednasser in #12390
- docs: show events emitted in an API route in the API reference by @shahednasser in #12392
- docs: generate OAS with events by @shahednasser in #12394
- docs: added bundled products guide by @shahednasser in #12332
- docs: add copy subscriber button by @shahednasser in #12405
- docs: generate references after adding copy subscriber button by @shahednasser in #12407
- docs: add support for keywords in references by @shahednasser in #12413
- docs: add class for header wrapper + keywords for payment guide by @shahednasser in #12414
- chore: update Medusa UI packages in docs on new release by @shahednasser in #12415
- docs: enable and send algolia events by @shahednasser in #12416
- docs: updates to middlewares and protected API routes + new chapter by @shahednasser in #12419
- docs: support version in events reference by @shahednasser in #12422
- docs-util: add note for providers by @shahednasser in #12432
- docs: support showing version of API route in API reference by @shahednasser in #12433
- docs: fix keyboard navigation in search by @shahednasser in #12434
- docs: support deprecated in API reference by @shahednasser in #12438
- docs-util: fix dependencies passed to auth providers by @shahednasser in #12445
- Update page.mdx to correct "th" to "the" by @dsvwilson in #12446
- docs: added saved payment methods with Stripe tutorial by @shahednasser in #12257
Chores
- chore(js-sdk): fix TSDocs of draft orders in JS SDK by @shahednasser in #12270
- chore(core-flows, types): add tsdocs for draft orders workflows by @shahednasser in #12279
- chore(types): claify province code format in TSDocs by @shahednasser in #12283
- chore(core-flows): fix note usage in TSDocs by @shahednasser in #12318
- chore: add tsdocs for events by @shahednasser in #12337
- chore(utils): fixes to events TSDocs by @shahednasser in #12342
- chore(types): add note in TSDocs of list and retrieve methods by @shahednasser in #12364
- feat(workflows-*): Allow to re run non idempotent but stored workflow with the same transaction id if considered done by @adrien2p in #12362
- chore(utils): fixes to events TSDocs by @shahednasser in #12395
- chore: Minor restructure of cart completion by @olivermrbl in #12353
- chore: update generate references action to generate / commit events by @shahednasser in #12411
- chore(utils): small fix to payload in TSDocs by @shahednasser in #12409
- chore(utils): add version tag to order edit events by @shahednasser in #12424
- test(workflow-engine): Ensure failed subscriber does not compensate workflows by @adrien2p in #12427
- chore(js-sdk,types,utils): updates to file TSDocs by @shahednasser in #12426
- chore(js-sdk, types): update TSDocs related to tax provider changes by @shahednasser in #12428
- chore(medusa): add version tag for new routes by @shahednasser in #12436
Other Changes
- docs: improve database operations docs + added data model repository reference by @shahednasser in #12273
- feat: internationalize hard-coded strings in product-create-general-section.tsx by @MentorFilou in #12264
- test: use jest shards and simplify test-cli-pipeline by @thetutlage in #12278
- fix(dashboard): Admin Global Search broken for variants by @AmbroziuBaban in #11877
- chore: Update teams.yml by @olivermrbl in #12355
- fix: rename Macedonia FYROM to North Macedonia by @edinabazi in #12375
- fix: Added missing admin dashboard dependencies by @pepijn-vanvlaanderen in #12346
- test(core-flows): Ensure productVariantUpdated failure compensate the workflow by @adrien2p in #12381
- fix(plugins, utils): Plugin admin folder loading with backslash on Windows by @Betanoir in #12418
New Contributors
- @AlexanderLindkjaer made their first contribution in #12272
- @MentorFilou made their first contribution in #12264
- @Obad94 made their first contribution in #12315
- @edinabazi made their first contribution in #12375
- @Betanoir made their first contribution in #12418
- @dsvwilson made their first contribution in #12446
Full Changelog: v2.7.1...v2.8.0