Skip to content

fix(core): Invalidate stock location channel id cache correctly - #5087

Open
ryandiginomad wants to merge 1 commit into
vendurehq:masterfrom
ryandiginomad:fix/multi-channel-stock-location-cache-invalidation
Open

fix(core): Invalidate stock location channel id cache correctly#5087
ryandiginomad wants to merge 1 commit into
vendurehq:masterfrom
ryandiginomad:fix/multi-channel-stock-location-cache-invalidation

Conversation

@ryandiginomad

@ryandiginomad ryandiginomad commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #3324.

MultiChannelStockLocationStrategy caches each StockLocation's channel ids for 7 days, but the cache was never actually invalidated, for two independent reasons:

1. The delete used a double-prefixed key (the defect reported in the issue). The StockLocationEvent subscription passed this.getCacheKey(entity.id) into Cache.delete(), but Cache.delete() applies the configured getKey function itself — the same way Cache.get() does — so the delete targeted MultiChannelStockLocationStrategy:StockLocationChannelIds:MultiChannelStockLocationStrategy:StockLocationChannelIds:<id>, which never exists, and the real entry lived out its full TTL. The fix is the one-liner proposed in the issue: pass the raw id. (I checked the rest of core for the same pattern — the other Cache consumers, e.g. customer-group-condition.ts and facet-value-checker.ts, already pass raw ids; this was the only occurrence.)

2. The operation that changes channel membership never emitted an invalidating event at all — surfaced by the follow-up investigation in this comment. assignStockLocationsToChannel() / removeStockLocationsFromChannel() delegate to ChannelService.assignToChannels() / removeFromChannels(), which emit ChangeChannelEvent, not StockLocationEvent. So even with the key fix, assigning a StockLocation to a new Channel leaves that channel reading the stale pre-assignment channel list: in a multi-channel setup, every newly added channel can show all products as OUT_OF_STOCK for up to 7 days (or until a server restart clears the in-memory cache).

The fix adds a second subscription in the strategy's init(), on ChangeChannelEvent filtered to entityType === StockLocation. Subscribing to the event — rather than patching the two service methods — keeps the invalidation inside the strategy that owns the cache, and covers every path that changes a StockLocation's channels, including plugins calling ChannelService directly.

Both changes live in the same init() block and are only meaningful together (the first makes deletes actually work, the second makes the right operation trigger them), so they ship as one PR.

Breaking changes

None. The only behavioural change is that saleable-stock reads now see channel-membership changes immediately, instead of after cache expiry or a server restart.

Testing

Unit (multi-channel-stock-location-strategy.spec.ts, new file, 6 cases, no DB): uses the real Cache wrapper over an in-memory Map — so the key-prefixing interaction under test behaves exactly as in production — plus a Subject-backed EventBus stub. On master, the three invalidation cases fail (update / assign / remove: the first because the delete misses its key, the other two because no subscription exists); with the fix all 6 pass. The remaining three pin existing behaviour: channel ids are cached after the first lookup, created events don't invalidate, and ChangeChannelEvents for other entity types don't invalidate.

E2E (stock-location.e2e-spec.ts, new describe): reproduces the real-world sequence from the issue thread — query a variant's saleable stock via the Shop API in a new channel's context (which caches the location's pre-assignment channel list), then assignStockLocationsToChannel, then query again. On master's dist this fails with OUT_OF_STOCK where IN_STOCK is expected; with the fix it passes. The 16 existing tests in the file are unaffected in both runs.

What I ran:

Result
New unit spec against master code 3 failed / 3 passed
New unit spec after the fix 6 passed
Full core unit suite 76 files, 1143 passed
stock-location.e2e-spec.ts against master dist 1 failed (the new test) / 16 passed
stock-location.e2e-spec.ts against rebuilt dist 17 passed
stock-control.e2e-spec.ts + stock-control-multi-location.e2e-spec.ts 46 passed

The pre-existing type error in order.service.ts:1460 that stops npm run build locally is unrelated (file untouched, reproduces on clean master).

🤖 AI assistance

I used Claude Code while working on this. I have reviewed every line of the change myself, and the testing table above reflects test runs I actually performed and observed — including verifying that each new regression test fails without its fix and passes with it.

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

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

The MultiChannelStockLocationStrategy caches each StockLocation's channel
ids for 7 days, but the cache was never actually invalidated:

- The StockLocationEvent subscription passed an already-prefixed key to
  Cache.delete(), which applies the configured getKey() again, so the
  delete targeted a double-prefixed key that never exists.
- Assigning a StockLocation to a Channel (or removing it) emits a
  ChangeChannelEvent, not a StockLocationEvent, so the operation that
  actually changes channel membership never triggered invalidation at
  all. A newly-added channel could see all products as out of stock
  until the entry expired or the server restarted.

Fixes vendurehq#3324
@vercel

vercel Bot commented Aug 3, 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 Aug 3, 2026 11:47pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The stock-location strategy now invalidates cached channel IDs for stock-location updates and channel-assignment changes. Unit tests cover cache reuse and event filtering. End-to-end tests cover cross-channel update protection and stock visibility after assigning a stock location to a channel.

Possibly related PRs

Suggested labels: type: bug 🐛, @vendure/core, T2: Elevated risk

Suggested reviewers: michaelbromley

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the cache invalidation fix for stock location channel IDs.
Description check ✅ Passed The description covers the changes, related issue, breaking changes, testing, checklist, and AI assistance disclosure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/e2e/stock-location.e2e-spec.ts`:
- Around line 500-501: Replace the fixed 100 ms delay in the stock-location test
with bounded polling of the shop query, repeatedly checking until the result is
IN_STOCK. Add a finite timeout or retry limit, and fail clearly if the expected
state is not observed within that bound.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 98249f36-a5a9-4f8a-85ff-6f50187a88e2

📥 Commits

Reviewing files that changed from the base of the PR and between b8004ae and e6ae02c.

📒 Files selected for processing (3)
  • packages/core/e2e/stock-location.e2e-spec.ts
  • packages/core/src/config/catalog/multi-channel-stock-location-strategy.spec.ts
  • packages/core/src/config/catalog/multi-channel-stock-location-strategy.ts

Comment on lines +500 to +501
// Wait a bit for the ChangeChannelEvent subscriber to invalidate the cache
await new Promise(resolve => setTimeout(resolve, 100));

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Replace the fixed delay with bounded polling.

Line 501 assumes that cache invalidation completes within 100 ms. Under CI load, the event handler can complete later and cause an intermittent failure. Poll the shop query until it returns IN_STOCK, with a bounded timeout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/e2e/stock-location.e2e-spec.ts` around lines 500 - 501, Replace
the fixed 100 ms delay in the stock-location test with bounded polling of the
shop query, repeatedly checking until the result is IN_STOCK. Add a finite
timeout or retry limit, and fail clearly if the expected state is not observed
within that bound.

@michaelbromley michaelbromley added the T2: Elevated risk Touches critical paths or has wide blast radius. Needs careful, owned work. label Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T2: Elevated risk Touches critical paths or has wide blast radius. Needs careful, owned work.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MultiChannelStockLocationStrategy Fails to Clear Cached Channel IDs on StockLocationEvent

2 participants