feat(medusa): Cart custom query strategy (#4083)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(medusa): Cart custom query strategy
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { AwilixContainer } from "awilix"
|
import { AwilixContainer } from "awilix"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
import { Cart } from "../../../../../../models"
|
||||||
import { CartService, LineItemService } from "../../../../../../services"
|
import { CartService, LineItemService } from "../../../../../../services"
|
||||||
|
import { WithRequiredProperty } from "../../../../../../types/common"
|
||||||
|
import { IdempotencyCallbackResult } from "../../../../../../types/idempotency-key"
|
||||||
import { FlagRouter } from "../../../../../../utils/flag-router"
|
import { FlagRouter } from "../../../../../../utils/flag-router"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "../../index"
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "../../index"
|
||||||
import { IdempotencyCallbackResult } from "../../../../../../types/idempotency-key"
|
|
||||||
import { WithRequiredProperty } from "../../../../../../types/common"
|
|
||||||
import { Cart } from "../../../../../../models"
|
|
||||||
|
|
||||||
export const CreateLineItemSteps = {
|
export const CreateLineItemSteps = {
|
||||||
STARTED: "started",
|
STARTED: "started",
|
||||||
|
|||||||
@@ -216,6 +216,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AfterLoad,
|
||||||
BeforeInsert,
|
BeforeInsert,
|
||||||
Column,
|
Column,
|
||||||
Entity,
|
Entity,
|
||||||
@@ -390,6 +391,13 @@ export class Cart extends SoftDeletableEntity {
|
|||||||
gift_card_total?: number
|
gift_card_total?: number
|
||||||
gift_card_tax_total?: number
|
gift_card_tax_total?: number
|
||||||
|
|
||||||
|
@AfterLoad()
|
||||||
|
private afterLoad(): void {
|
||||||
|
if (this.payment_sessions) {
|
||||||
|
this.payment_session = this.payment_sessions.find((p) => p.is_selected)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeInsert()
|
@BeforeInsert()
|
||||||
private beforeInsert(): void {
|
private beforeInsert(): void {
|
||||||
this.id = generateEntityId(this.id, "cart")
|
this.id = generateEntityId(this.id, "cart")
|
||||||
|
|||||||
@@ -1,16 +1,56 @@
|
|||||||
import { ExtendedFindConfig } from "@medusajs/types"
|
import { objectToStringPath } from "@medusajs/utils"
|
||||||
|
import { flatten, groupBy, map, merge } from "lodash"
|
||||||
|
import { FindManyOptions, FindOptionsRelations, In } from "typeorm"
|
||||||
import { dataSource } from "../loaders/database"
|
import { dataSource } from "../loaders/database"
|
||||||
import { Cart } from "../models"
|
import { Cart } from "../models"
|
||||||
|
|
||||||
export const CartRepository = dataSource.getRepository(Cart).extend({
|
export const CartRepository = dataSource.getRepository(Cart).extend({
|
||||||
async findOne(options: ExtendedFindConfig<Cart>) {
|
async findWithRelations(
|
||||||
const [cart] = await this.find(options)
|
relations: FindOptionsRelations<Cart> = {},
|
||||||
|
optionsWithoutRelations: Omit<FindManyOptions<Cart>, "relations"> = {}
|
||||||
|
): Promise<Cart[]> {
|
||||||
|
const entities = await this.find(optionsWithoutRelations)
|
||||||
|
const entitiesIds = entities.map(({ id }) => id)
|
||||||
|
|
||||||
if (cart?.payment_sessions?.length) {
|
const groupedRelations = {}
|
||||||
cart.payment_session = cart.payment_sessions.find((p) => p.is_selected)!
|
for (const rel of objectToStringPath(relations)) {
|
||||||
|
const [topLevel] = rel.split(".")
|
||||||
|
if (groupedRelations[topLevel]) {
|
||||||
|
groupedRelations[topLevel].push(rel)
|
||||||
|
} else {
|
||||||
|
groupedRelations[topLevel] = [rel]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cart
|
const entitiesIdsWithRelations = await Promise.all(
|
||||||
|
Object.entries(groupedRelations).map(async ([_, rels]) => {
|
||||||
|
return this.find({
|
||||||
|
where: { id: In(entitiesIds) },
|
||||||
|
select: ["id"],
|
||||||
|
relations: rels as string[],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
).then(flatten)
|
||||||
|
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||||
|
|
||||||
|
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||||
|
return map(entitiesAndRelationsById, (entityAndRelations) =>
|
||||||
|
merge({}, ...entityAndRelations)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
async findOneWithRelations(
|
||||||
|
relations: FindOptionsRelations<Cart> = {},
|
||||||
|
optionsWithoutRelations: Omit<FindManyOptions<Cart>, "relations"> = {}
|
||||||
|
): Promise<Cart> {
|
||||||
|
// Limit 1
|
||||||
|
optionsWithoutRelations.take = 1
|
||||||
|
|
||||||
|
const result = await this.findWithRelations(
|
||||||
|
relations,
|
||||||
|
optionsWithoutRelations
|
||||||
|
)
|
||||||
|
return result[0]
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default CartRepository
|
export default CartRepository
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ describe("CartService", () => {
|
|||||||
describe("retrieve", () => {
|
describe("retrieve", () => {
|
||||||
let result
|
let result
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: () => Promise.resolve({ id: IdMap.getId("emptyCart") }),
|
findOneWithRelations: () =>
|
||||||
|
Promise.resolve({ id: IdMap.getId("emptyCart") }),
|
||||||
})
|
})
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
@@ -81,13 +82,15 @@ describe("CartService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls cart model functions", () => {
|
it("calls cart model functions", () => {
|
||||||
expect(cartRepository.findOne).toHaveBeenCalledTimes(1)
|
expect(cartRepository.findOneWithRelations).toHaveBeenCalledTimes(1)
|
||||||
expect(cartRepository.findOne).toHaveBeenCalledWith({
|
expect(cartRepository.findOneWithRelations).toHaveBeenCalledWith(
|
||||||
relationLoadStrategy: "query",
|
{},
|
||||||
where: { id: IdMap.getId("emptyCart") },
|
{
|
||||||
select: undefined,
|
where: { id: IdMap.getId("emptyCart") },
|
||||||
relations: undefined,
|
select: undefined,
|
||||||
})
|
relations: undefined,
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -126,7 +129,9 @@ describe("CartService", () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(cartRepository.findOne).toBeCalledTimes(1)
|
expect(cartRepository.findOne).toBeCalledTimes(1)
|
||||||
expect(cartRepository.findOne).toBeCalledWith({ where: { id } })
|
expect(cartRepository.findOne).toBeCalledWith({
|
||||||
|
where: { id },
|
||||||
|
})
|
||||||
|
|
||||||
expect(cartRepository.save).toBeCalledTimes(1)
|
expect(cartRepository.save).toBeCalledTimes(1)
|
||||||
expect(cartRepository.save).toBeCalledWith({
|
expect(cartRepository.save).toBeCalledWith({
|
||||||
@@ -164,7 +169,7 @@ describe("CartService", () => {
|
|||||||
|
|
||||||
const addressRepository = MockRepository({
|
const addressRepository = MockRepository({
|
||||||
create: (c) => c,
|
create: (c) => c,
|
||||||
findOne: (id) => {
|
findOneWithRelations: (id) => {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
first_name: "LeBron",
|
first_name: "LeBron",
|
||||||
@@ -343,7 +348,7 @@ describe("CartService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
if (q.where.id === IdMap.getId("cartWithLine")) {
|
if (q.where.id === IdMap.getId("cartWithLine")) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
id: IdMap.getId("cartWithLine"),
|
id: IdMap.getId("cartWithLine"),
|
||||||
@@ -584,7 +589,7 @@ describe("CartService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
if (q.where.id === IdMap.getId("cartWithLine")) {
|
if (q.where.id === IdMap.getId("cartWithLine")) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
id: IdMap.getId("cartWithLine"),
|
id: IdMap.getId("cartWithLine"),
|
||||||
@@ -670,7 +675,7 @@ describe("CartService", () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
if (q.where.id === IdMap.getId("withShipping")) {
|
if (q.where.id === IdMap.getId("withShipping")) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
shipping_methods: [
|
shipping_methods: [
|
||||||
@@ -813,7 +818,7 @@ describe("CartService", () => {
|
|||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
if (q.where.id === "withpays") {
|
if (q.where.id === "withpays") {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
payment_sessions: [
|
payment_sessions: [
|
||||||
@@ -844,25 +849,25 @@ describe("CartService", () => {
|
|||||||
cartService.setPaymentSessions = jest.fn()
|
cartService.setPaymentSessions = jest.fn()
|
||||||
await cartService.update("withpays", {})
|
await cartService.update("withpays", {})
|
||||||
|
|
||||||
expect(cartRepository.findOne).toHaveBeenCalledWith(
|
expect(cartRepository.findOneWithRelations).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
relations: {
|
billing_address: true,
|
||||||
billing_address: true,
|
customer: true,
|
||||||
customer: true,
|
discounts: {
|
||||||
discounts: {
|
rule: true,
|
||||||
rule: true,
|
|
||||||
},
|
|
||||||
gift_cards: true,
|
|
||||||
items: {
|
|
||||||
variant: {
|
|
||||||
product: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
payment_sessions: true,
|
|
||||||
region: { countries: true },
|
|
||||||
shipping_address: true,
|
|
||||||
shipping_methods: true,
|
|
||||||
},
|
},
|
||||||
|
gift_cards: true,
|
||||||
|
items: {
|
||||||
|
variant: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
payment_sessions: true,
|
||||||
|
region: { countries: true },
|
||||||
|
shipping_address: true,
|
||||||
|
shipping_methods: true,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
select: undefined,
|
select: undefined,
|
||||||
where: {
|
where: {
|
||||||
id: "withpays",
|
id: "withpays",
|
||||||
@@ -907,7 +912,7 @@ describe("CartService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
if (q.where.id === IdMap.getId("cannot")) {
|
if (q.where.id === IdMap.getId("cannot")) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
items: [
|
items: [
|
||||||
@@ -1019,7 +1024,7 @@ describe("CartService", () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: () => Promise.resolve({}),
|
findOneWithRelations: () => Promise.resolve({}),
|
||||||
})
|
})
|
||||||
const cartService = new CartService({
|
const cartService = new CartService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
@@ -1091,7 +1096,7 @@ describe("CartService", () => {
|
|||||||
|
|
||||||
describe("updateBillingAddress", () => {
|
describe("updateBillingAddress", () => {
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: () =>
|
findOneWithRelations: () =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
region: { countries: [{ iso_2: "us" }] },
|
region: { countries: [{ iso_2: "us" }] },
|
||||||
}),
|
}),
|
||||||
@@ -1153,7 +1158,7 @@ describe("CartService", () => {
|
|||||||
|
|
||||||
describe("updateShippingAddress", () => {
|
describe("updateShippingAddress", () => {
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: () =>
|
findOneWithRelations: () =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
region: { countries: [{ iso_2: "us" }] },
|
region: { countries: [{ iso_2: "us" }] },
|
||||||
}),
|
}),
|
||||||
@@ -1288,7 +1293,7 @@ describe("CartService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: () =>
|
findOneWithRelations: () =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@@ -1415,7 +1420,7 @@ describe("CartService", () => {
|
|||||||
|
|
||||||
describe("setPaymentSession", () => {
|
describe("setPaymentSession", () => {
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
if (q.where.id === IdMap.getId("cartWithLine")) {
|
if (q.where.id === IdMap.getId("cartWithLine")) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
total: 100,
|
total: 100,
|
||||||
@@ -1623,7 +1628,7 @@ describe("CartService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
if (q.where.id === IdMap.getId("cart-to-filter")) {
|
if (q.where.id === IdMap.getId("cart-to-filter")) {
|
||||||
return Promise.resolve(cart3)
|
return Promise.resolve(cart3)
|
||||||
}
|
}
|
||||||
@@ -1829,7 +1834,7 @@ describe("CartService", () => {
|
|||||||
const cartWithCustomSO = buildCart("cart-with-custom-so")
|
const cartWithCustomSO = buildCart("cart-with-custom-so")
|
||||||
|
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
switch (q.where.id) {
|
switch (q.where.id) {
|
||||||
case IdMap.getId("lines"):
|
case IdMap.getId("lines"):
|
||||||
return Promise.resolve(cart3)
|
return Promise.resolve(cart3)
|
||||||
@@ -2028,7 +2033,7 @@ describe("CartService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
if (q.where.id === IdMap.getId("with-d")) {
|
if (q.where.id === IdMap.getId("with-d")) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
id: IdMap.getId("cart"),
|
id: IdMap.getId("cart"),
|
||||||
@@ -2517,7 +2522,7 @@ describe("CartService", () => {
|
|||||||
|
|
||||||
describe("removeDiscount", () => {
|
describe("removeDiscount", () => {
|
||||||
const cartRepository = MockRepository({
|
const cartRepository = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (rel, q) => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
id: IdMap.getId("cart"),
|
id: IdMap.getId("cart"),
|
||||||
discounts: [
|
discounts: [
|
||||||
|
|||||||
@@ -231,13 +231,15 @@ class CartService extends TransactionBaseService {
|
|||||||
const cartRepo = this.activeManager_.withRepository(this.cartRepository_)
|
const cartRepo = this.activeManager_.withRepository(this.cartRepository_)
|
||||||
|
|
||||||
const query = buildQuery({ id: cartId }, options)
|
const query = buildQuery({ id: cartId }, options)
|
||||||
query.relationLoadStrategy = "query"
|
|
||||||
|
|
||||||
if ((options.select || []).length === 0) {
|
if ((options.select || []).length === 0) {
|
||||||
query.select = undefined
|
query.select = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = await cartRepo.findOne(query)
|
const queryRelations = { ...query.relations }
|
||||||
|
delete query.relations
|
||||||
|
|
||||||
|
const raw = await cartRepo.findOneWithRelations(queryRelations, query)
|
||||||
|
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
|
|||||||
Reference in New Issue
Block a user