### What
Add a new `once` allocation strategy to promotions that limits application to a maximum number of items across the entire cart, rather than per line item.
### Why
Merchants want to create promotions that apply to a limited number of items across the entire cart. For example:
- "Get $10 off, applied to one item only"
- "20% off up to 2 items in your cart"
Current allocation strategies:
- `each`: Applies to each line item independently (respects `max_quantity` per item)
- `across`: Distributes proportionally across all items
Neither supports limiting total applications across the entire cart.
### How
Add `once` to the `ApplicationMethodAllocation` enum.
Behavior:
- Applies promotion to maximum `max_quantity` items across entire cart
- Always prioritizes lowest-priced eligible items first
- Distributes sequentially across items until quota exhausted
- Requires `max_quantity` field to be set
### Example Usage
**Scenario 1: Fixed discount**
```javascript
{
type: "fixed",
allocation: "once",
value: 10, // $10 off
max_quantity: 2 // Apply to 2 items max across cart
}
Cart:
- Item A: 3 units @ $100/unit
- Item B: 5 units @ $50/unit (lowest price)
Result: $20 discount on Item B (2 units × $10)
```
**Scenario 2: Distribution across items**
```javascript
{
type: "fixed",
allocation: "once",
value: 5,
max_quantity: 4
}
Cart:
- Item A: 2 units @ $50/unit
- Item B: 3 units @ $60/unit
Result:
- Item A: $10 discount (2 units × $5)
- Item B: $10 discount (2 units × $5, remaining quota)
```
**Scenario 3: Percentage discount - single item**
```javascript
{
type: "percentage",
allocation: "once",
value: 20, // 20% off
max_quantity: 3 // Apply to 3 items max
}
Cart:
- Item A: 5 units @ $100/unit
- Item B: 4 units @ $50/unit (lowest price)
Result: $30 discount on Item B (3 units × $50 × 20% = $30)
```
**Scenario 4: Percentage discount - distributed across items**
```javascript
{
type: "percentage",
allocation: "once",
value: 15, // 15% off
max_quantity: 5
}
Cart:
- Item A: 2 units @ $40/unit (lowest price)
- Item B: 4 units @ $80/unit
Result:
- Item A: $12 discount (2 units × $40 × 15% = $12)
- Item B: $36 discount (3 units × $80 × 15% = $36, remaining quota)
Total: $48 discount
```
**Scenario 5: Percentage with max_quantity = 1**
```javascript
{
type: "percentage",
allocation: "once",
value: 25, // 25% off
max_quantity: 1 // Only one item
}
Cart:
- Item A: 3 units @ $60/unit
- Item B: 2 units @ $30/unit (lowest price)
Result: $7.50 discount on Item B (1 unit × $30 × 25%)
```
**What**
- implement promotion usage limits per customer/email
- fix registering spend usage over the limit
- fix type errors in promotion module tests
**How**
- introduce a new type of campaign budget that can be defined by an attribute such as customer id or email
- add `CampaignBudgetUsage` entity to keep track of the number of uses per attribute value
- update `registerUsage` and `computeActions` in the promotion module to work with the new type
- update `core-flows` to pass context needed for usage calculation to the promotion module
**Breaking**
- registering promotion usage now throws (and cart complete fails) if the budget limit is exceeded or if the cart completion would result in a breached limit
---
CLOSES CORE-1172
CLOSES CORE-1173
CLOSES CORE-1174
CLOSES CORE-1175
Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
**What**
After lot of investigation, we finally found one of our performance regerssion point (see [here](https://github.com/mikro-orm/mikro-orm/issues/6905)), this pr downgrade mikro orm and move the strategy back to select in where needed
**What**
It seems that for some reason the weak map fail in some scenario, but after investigation, the usage of map would not have a bad impact as it will be released after the Distributed transaction if finished. Therefore, falling back to Map instead
FIXES https://github.com/medusajs/medusa/issues/13654
NOTE: Waiting for the user feedback as he is also using node 18. We also use the exact same pattern in all our core flows without issues 🤔
RESOLVES CORE-1153
**What**
- This pr mainly lay the foundation the caching layer. It comes with a modules (built in memory cache) and a redis provider.
- Apply caching to few touch point to test
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
PARTIALLY RESOLVES CORE-1156
**What**
Improve upsertWithReplace to batch as much as possible what can be batched. Performance of this method will be much greater specially for cases with maybe entities and batch (e.g we seen many cases where they bulk product with hundreds variants and options etc)
for example let take the following object:
- entity 1
- entity 2 []
- entity 3 []
- entity 2 []
- entity 3 []
here all entity 3 will be batched and all entity 2 will be batched
I ve also added a pretty detail test that check all the stage and what is batched or not with many comments so that it is less harder to consume and remember in the future
Also includes:
- mikro orm upgade (issues found and fixes)
- order module hooks fixes
**NOTE**
It was easier for now to do this instead of rewriting the different areas where it is being used, also, maybe it means that we will have closer performance to what we would expect to have natively
**NOTE 2**
Also fix the fact that integration tests of the core packages never ran 😂
* chore(): remove ssl_mode from url and also use sslmode
* improve regexp
* chore(): remove ssl_mode from url and also use sslmode
* chore(): remove ssl_mode from url and also use sslmode
* Update SSL mode configuration in changeset
Removed 'ssl_mode' from URL and replaced it with 'sslmode'.
* fix(utils,core-flows): subtotal calculation and returns location
* changeset
* fix test
* var
* rm extra field from test
* fix original total
* fix partial refunds and pending difference
* fix test
* fix test
* test
* extract to util
* original total and update payment when receive return
* original_subtotal
* default fields
* test
* calculate pending difference
* revert claims test
* pending difference
* creadit line fix
* if
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* Clarify test description and improve CI
* chore(): Upgrade mikro orm
* handle 'null' value for big number props
* 6.5.2
* remove only
* fix pricing module rule value
* switch select in strategy for balances
* revert to select in strategy for order module
* fix defining DML ManyToOne
* fix define relationship
* test fix
* more fixes
* change order strategy to balanced
* change order strategy to balanced
* prevent unnecessary manager fork
* revert generated www changes
* remove unnecessary changes
* Create real-cobras-deny.md
* address feedback
---------
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
RESOLVES CORE-1171
**What**
- Reduced async overhead for objects with mixed sync/async properties
- Lower memory pressure from eliminated promise allocations
- Faster primitive value processing with early returns
- Better concurrency through selective batching of truly async operations
- Event loop friendly behavior preventing artificial delays
- Reduced memory allocation from eliminated duplicate processing
- Decreased GC pressure from WeakMap-based caching instead of map
**note**
Now, `resolveValue`always treat every resoltion as sync operation unless it is not, meaning we do not create promise overhead when not necessary and only when actually treating with promises
* feat(dashboard,types,utils): refine order details summary
* fix tests
* changeset
* ui corrections
* tests again
* weird tests failing
* revert update to subtotal
* revert http tests too
* comments
* move credit lines so it makes more sense
* remove currency codes and add bold prices
* add new properties in default for storefront
* minor to patch
* remove bold on things that should be
* olis comment about taxes
* remove bold from shipping
* feat: custom logger
* mock log
* unit test
* FF and jobs loader
* unit test
* add to ResourceLoader
* get from container
* mock
* rm log
* default logger mock
* link loaders, express
* comments
* initialize container as first step
* db conn
* test
* initialize start
* plugin build using default logger
* ignore .medusa
* revert ignroe
---------
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
- fix: make Metadata Variant and Product Variant static columns use the new function, so they don't fail later with the create / update product zod schema
- chore: add tests for processAsJson fucntion
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
** What
- Allow auto-loaded Medusa files to export a config object.
- Currently supports isDisabled to control loading.
- new instance `FeatureFlag` exported by `@medusajs/framework/utils`
- `feature-flags` is now a supported folder for medusa projects, modules, providers and plugins. They will be loaded and added to `FeatureFlag`
** Why
- Enables conditional loading of routes, migrations, jobs, subscribers, workflows, and other files based on feature flags.
```ts
// /src/feature-flags
import { FlagSettings } from "@medusajs/framework/feature-flags"
const CustomFeatureFlag: FlagSettings = {
key: "custom_feature",
default_val: false,
env_key: "FF_MY_CUSTOM_FEATURE",
description: "Enable xyz",
}
export default CustomFeatureFlag
```
```ts
// /src/modules/my-custom-module/migration/Migration20250822135845.ts
import { FeatureFlag } from "@medusajs/framework/utils"
export class Migration20250822135845 extends Migration {
override async up(){ }
override async down(){ }
}
defineFileConfig({
isDisabled: () => !FeatureFlag.isFeatureEnabled("custom_feature")
})
```
* fix(utils): fix promotion case of each allocation not applying its amount
* chore: fixed tests
---------
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
* feat: add view_configurations feature flag
- Add feature flag provider and hooks to admin dashboard
- Add backend API endpoint for feature flags
- Create view_configurations feature flag (disabled by default)
- Update order list table to use legacy version when flag is disabled
- Can be enabled with MEDUSA_FF_VIEW_CONFIGURATIONS=true env var
* fix: naming
* fix: feature flags unauthenticated
* fix: add test
* feat: add settings module
* fix: deps
* fix: cleanup
* fix: add more tetsts
* fix: rm changelog
* fix: deps
* fix: add settings module to default modules list
* fix: pr comments
* fix: deps,build
* fix: alias
* fix: tests
* fix: update snapshots
* chore(types, api): support shipping option type api endpoints
* core flows
* api
* typos
* compiler errors
* integration tests
* remove metadata
* changeset
* modify test
* upsert
* change remote query
* minor to patch
* description optional
* description optional
* woops my bad
* my bad again
---------
Co-authored-by: william bouchard <williambouchard@williams-MacBook-Pro.local>