diff --git a/src/services/ProductStripeService.js b/src/services/ProductStripeService.js index 07aacd6..c3023ff 100644 --- a/src/services/ProductStripeService.js +++ b/src/services/ProductStripeService.js @@ -152,9 +152,10 @@ export class ProductStripeService { if (Array.isArray(updates.variants)) { const incomingVariants = updates.variants const existingVariants = Array.isArray(existingProduct.variants) ? existingProduct.variants : [] + const existingVariantsMap = new Map(existingVariants.filter(ev => ev.id).map(ev => [ev.id, ev])) const results = await Promise.all(incomingVariants.map(async (v) => { - const prior = v.id ? existingVariants.find(ev => ev.id === v.id) : undefined + const prior = v.id ? existingVariantsMap.get(v.id) : undefined const wantsCustom = !!v.hasCustomPrice && typeof v.price === 'number' && v.price > 0 if (wantsCustom) { @@ -200,9 +201,10 @@ export class ProductStripeService { if (Array.isArray(updates.variants2)) { const incomingVariants = updates.variants2 const existingVariants = Array.isArray(existingProduct.variants2) ? existingProduct.variants2 : [] + const existingVariantsMap = new Map(existingVariants.filter(ev => ev.id).map(ev => [ev.id, ev])) const results = await Promise.all(incomingVariants.map(async (v) => { - const prior = v.id ? existingVariants.find(ev => ev.id === v.id) : undefined + const prior = v.id ? existingVariantsMap.get(v.id) : undefined const wantsCustom = !!v.hasCustomPrice && typeof v.price === 'number' && v.price > 0 if (wantsCustom) { diff --git a/tests/services/ProductStripeService.perf.test.js b/tests/services/ProductStripeService.perf.test.js new file mode 100644 index 0000000..8a18523 --- /dev/null +++ b/tests/services/ProductStripeService.perf.test.js @@ -0,0 +1,61 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { ProductStripeService } from '../../src/services/ProductStripeService.js' + +class MockStripeService { + async createProduct() { return { id: 'prod_123' } } + async createPrice(data) { return { id: `price_${Math.random()}` } } + async archivePrice() { return true } +} + +describe('ProductStripeService Performance', () => { + let stripeService + let productStripeService + + beforeEach(() => { + vi.clearAllMocks() + stripeService = new MockStripeService() + productStripeService = new ProductStripeService(stripeService) + }) + + it('should verify variant update performance', async () => { + // Generate a large number of existing variants + const NUM_VARIANTS = 10000; + const existingVariants = Array.from({ length: NUM_VARIANTS }).map((_, i) => ({ + id: `var_${i}`, + name: `Variant ${i}`, + price: 10 + i, + hasCustomPrice: true, + stripePriceId: `price_${i}` + })); + + const existingVariants2 = Array.from({ length: NUM_VARIANTS }).map((_, i) => ({ + id: `var2_${i}`, + name: `Variant2 ${i}`, + price: 100 + i, + hasCustomPrice: true, + stripePriceId: `price2_${i}` + })); + + const existingProduct = { + stripeProductId: 'prod_123', + stripePriceId: 'base_price_123', + variants: existingVariants, + variants2: existingVariants2 + }; + + // Updates with the same variants, to trigger the O(N^2) behavior + const updates = { + variants: existingVariants.map(v => ({ ...v, price: v.price + 1 })), + variants2: existingVariants2.map(v => ({ ...v, price: v.price + 1 })) + }; + + console.log(`--- Starting Performance Test with ${NUM_VARIANTS} variants (primary and secondary) ---`); + const start = performance.now(); + await productStripeService.updateProductVariants(existingProduct, updates, stripeService); + const end = performance.now(); + + console.log(`Update time: ${(end - start).toFixed(2)}ms`); + + expect(true).toBe(true); + }) +})