Skip to content

fix(core,elasticsearch-plugin): Include channelId in productInStock cache key - #4214

Merged
michaelbromley merged 11 commits into
masterfrom
fix/oss-19-elasticsearch-product-in-stock-channel-cache
Feb 21, 2026
Merged

fix(core,elasticsearch-plugin): Include channelId in productInStock cache key#4214
michaelbromley merged 11 commits into
masterfrom
fix/oss-19-elasticsearch-product-in-stock-channel-cache

Conversation

@BibiSebi

@BibiSebi BibiSebi commented Feb 7, 2026

Copy link
Copy Markdown
Collaborator

Description

The getProductInStockValue() / productVariantsStock cache in indexer.controller.ts used a key that didn't include the channel ID. In multi-channel setups, this caused stale productInStock values from one channel to leak into another during reindexing — products appeared "in stock" in channels where they had no stock.

Fixed by including ctx.channelId in the cache key in both:

  • @vendure/elasticsearch-plugin (elastic-index-product-in-stock-{channelId}-...)
  • @vendure/core default-search-plugin (productVariantsStock-{channelId}-...)

Fixes vendurehq/community-plugins#1

Breaking changes

None.

Testing

Automated

E2e tests added for both plugins:

elasticsearch-plugin.e2e-spec.ts (multi-channel productInStock cache):

  1. Creates a second channel
  2. Creates a product with stock in the default channel only
  3. Assigns the product to the second channel (no stock)
  4. Reindexes both channels
  5. Verifies the product is inStock in Channel 1
  6. Verifies the product is not returned with inStock: true in Channel 2
  7. Verifies the product is returned with inStock: false in Channel 2

default-search-plugin.e2e-spec.ts (multi-channel productInStock cache):

  • Same scenario as above, targeting the default search plugin

All tests confirmed to fail without the fix and pass with it.

Running the tests

# Elasticsearch plugin
cd packages/elasticsearch-plugin && npm run e2e -- elasticsearch-plugin.e2e-spec.ts -t "multi-channel productInStock"

# Default search plugin
cd packages/core && npm run e2e -- default-search-plugin.e2e-spec.ts -t "multi-channel productInStock"

Manual local testing

Prerequisites

  1. Docker running with Postgres (docker-compose up -d postgres_16)
  2. For elasticsearch-plugin testing, also start Elasticsearch (docker-compose up -d elasticsearch)

Setup

  1. In packages/dev-server/dev-config.ts, enable one of:
    • DefaultSearchPlugin.init({ bufferUpdates: false, indexStockStatus: true })
    • Or uncomment ElasticsearchPlugin.init({ ... })
  2. Rebuild packages (worker runs from compiled JS, not TS source):
    cd packages/core && npm run build
    # If testing elasticsearch-plugin:
    cd packages/elasticsearch-plugin && npm run build
  3. Populate and start the dev server:
    cd packages/dev-server
    DB=postgres npm run populate
    DB=postgres npm run dev

Screenshots

Default Channel

default-search-channel-default-false default-search-channel-default-true

Channel 2

default-search-channel-2-false default-search-channel-2-true

Checklist

📌 Always:

  • I have set a clear title
  • My PR is small and contains a single feature
  • I have checked my own PR

👍 Most of the time:

  • I have added or updated test cases
  • I have updated the README if needed

@coderabbitai

coderabbitai Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

This PR scopes product/variant in-stock caching to the current channel by adding ctx.channelId to the cache keys in the default-search-plugin and elasticsearch-plugin indexer controllers. It also adds new multi-channel e2e tests (duplicated in two test files) that create a second channel, assign a product/variant with stock to channels, reindex channels, and verify inStock behavior per channel. The tests import GlobalFlag from generated admin GraphQL types.

Possibly related issues

  • vendurehq/vendure#4129 — The change adds ctx.channelId to the in-stock cache key, directly addressing the reported multi-channel caching bug where products were incorrectly shown as in-stock across channels.

Suggested reviewers

  • michaelbromley
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (9 files):

⚔️ packages/core/e2e/default-search-plugin.e2e-spec.ts (content)
⚔️ packages/core/src/common/constants.ts (content)
⚔️ packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts (content)
⚔️ packages/core/src/service/helpers/order-calculator/order-calculator.ts (content)
⚔️ packages/core/src/service/helpers/product-price-applicator/product-price-applicator.ts (content)
⚔️ packages/core/src/service/services/order.service.ts (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx (content)
⚔️ packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts (content)
⚔️ packages/elasticsearch-plugin/src/indexing/indexer.controller.ts (content)

These conflicts must be resolved before merging into master.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: including channelId in the productInStock cache key across both core and elasticsearch-plugin.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering objectives, breaking changes, testing details, manual testing instructions, and checklist items per the template.
Linked Issues check ✅ Passed The PR fully addresses the linked issue by including channelId in cache keys for both elasticsearch-plugin and core default-search-plugin, and includes comprehensive e2e tests for multi-channel scenarios.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the cache key issue and adding related e2e tests; minor import reorganization is incidental to the fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/oss-19-elasticsearch-product-in-stock-channel-cache
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch fix/oss-19-elasticsearch-product-in-stock-channel-cache
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel

vercel Bot commented Feb 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vendure-storybook Ready Ready Preview, Comment Feb 21, 2026 9:36pm

Request Review

@BibiSebi BibiSebi changed the title fix(elasticsearch-plugin): Include channelId in productInStock cache key fix(elasticsearch-plugin,core): Include channelId in productInStock cache key Feb 13, 2026
@BibiSebi
BibiSebi force-pushed the fix/oss-19-elasticsearch-product-in-stock-channel-cache branch from ac0df9c to a2f6eac Compare February 13, 2026 13:07
@BibiSebi BibiSebi changed the title fix(elasticsearch-plugin,core): Include channelId in productInStock cache key fix(core,elasticsearch-plugin): Include channelId in productInStock cache key Feb 13, 2026
@BibiSebi
BibiSebi force-pushed the fix/oss-19-elasticsearch-product-in-stock-channel-cache branch from abb49bb to 46b2bf9 Compare February 13, 2026 14:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts (1)

477-493: ⚠️ Potential issue | 🟡 Minor

Inner query fetches all product variants regardless of channel.

The query at lines 479–485 retrieves variants by productId without filtering by the current channel. This means productInStock may reflect stock from variants not assigned to ctx.channel, even though the cache key is scoped per-channel. Since getSaleableStockLevel(ctx, v) only checks stock by variant ID and doesn't validate channel membership, variants outside the current channel are included in the result. This is pre-existing behavior, but verify in your e2e tests that the product stock status reflects expected results for each channel independently.

🧹 Nitpick comments (1)
packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts (1)

1570-1644: Test setup is well-structured and correctly models the multi-channel stock scenario.

One minor note: adminClient is left with STOCK_CHANNEL_TOKEN after beforeAll (line 1641). Since the test cases only use shopClient, this doesn't affect the current tests. However, if additional tests are appended after this describe block in the future, the leaked admin channel token could cause confusing failures. Consider resetting it at the end of beforeAll or adding an afterAll.

♻️ Optional: reset adminClient token at end of beforeAll
             // Reindex the second channel
             adminClient.setChannelToken(STOCK_CHANNEL_TOKEN);
             await adminClient.query<Codegen.ReindexMutation>(REINDEX);
             await awaitRunningJobs(adminClient);
+
+            // Reset to default channel token to avoid leaking state
+            adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
         });

@BibiSebi

Copy link
Copy Markdown
Collaborator Author

fixing the test so that it resets the adminClient

@michaelbromley michaelbromley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

@michaelbromley
michaelbromley enabled auto-merge (squash) February 21, 2026 21:34
@michaelbromley
michaelbromley merged commit b3d1903 into master Feb 21, 2026
21 checks passed
@michaelbromley
michaelbromley deleted the fix/oss-19-elasticsearch-product-in-stock-channel-cache branch February 21, 2026 21:46
@vendure-ci-automation-bot vendure-ci-automation-bot Bot locked and limited conversation to collaborators Feb 21, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ElasticsearchPlugin] Products incorrectly shown as "in stock" in multi-channel setup when using groupByProduct

2 participants