feat(core, event-bus): Compensate emit event step utility (#13281)
* feat(core, event-bus): Compensate emit event step utility * tests * Update changeset to remove integration-tests-modules Removed integration-tests-modules from changeset. * revert test script
This commit is contained in:
1 parent
73a25abecb
commit
fc49253273
11 files changed
+534
-9
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/event-bus-local": patch
|
||||||
|
"@medusajs/event-bus-redis": patch
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(core, event-bus): Compensate emit event step utility
|
||||||
@@ -12,6 +12,7 @@ import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
|||||||
import {
|
import {
|
||||||
ICartModuleService,
|
ICartModuleService,
|
||||||
ICustomerModuleService,
|
ICustomerModuleService,
|
||||||
|
IEventBusModuleService,
|
||||||
IFulfillmentModuleService,
|
IFulfillmentModuleService,
|
||||||
IInventoryService,
|
IInventoryService,
|
||||||
IPaymentModuleService,
|
IPaymentModuleService,
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
IRegionModuleService,
|
IRegionModuleService,
|
||||||
ISalesChannelModuleService,
|
ISalesChannelModuleService,
|
||||||
IStockLocationService,
|
IStockLocationService,
|
||||||
|
Message,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
@@ -60,6 +62,7 @@ medusaIntegrationTestRunner({
|
|||||||
let defaultRegion
|
let defaultRegion
|
||||||
let customer, storeHeadersWithCustomer
|
let customer, storeHeadersWithCustomer
|
||||||
let setPricingContextHook: any
|
let setPricingContextHook: any
|
||||||
|
let eventBus: IEventBusModuleService
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
appContainer = getContainer()
|
appContainer = getContainer()
|
||||||
@@ -70,6 +73,7 @@ medusaIntegrationTestRunner({
|
|||||||
productModule = appContainer.resolve(Modules.PRODUCT)
|
productModule = appContainer.resolve(Modules.PRODUCT)
|
||||||
pricingModule = appContainer.resolve(Modules.PRICING)
|
pricingModule = appContainer.resolve(Modules.PRICING)
|
||||||
paymentModule = appContainer.resolve(Modules.PAYMENT)
|
paymentModule = appContainer.resolve(Modules.PAYMENT)
|
||||||
|
eventBus = appContainer.resolve(Modules.EVENT_BUS)
|
||||||
fulfillmentModule = appContainer.resolve(Modules.FULFILLMENT)
|
fulfillmentModule = appContainer.resolve(Modules.FULFILLMENT)
|
||||||
inventoryModule = appContainer.resolve(Modules.INVENTORY)
|
inventoryModule = appContainer.resolve(Modules.INVENTORY)
|
||||||
stockLocationModule = appContainer.resolve(Modules.STOCK_LOCATION)
|
stockLocationModule = appContainer.resolve(Modules.STOCK_LOCATION)
|
||||||
@@ -747,6 +751,7 @@ medusaIntegrationTestRunner({
|
|||||||
paymentSession.id
|
paymentSession.id
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should complete cart when payment webhook and storefront are called in simultaneously", async () => {
|
it("should complete cart when payment webhook and storefront are called in simultaneously", async () => {
|
||||||
const salesChannel = await scModuleService.createSalesChannels({
|
const salesChannel = await scModuleService.createSalesChannels({
|
||||||
name: "Webshop",
|
name: "Webshop",
|
||||||
@@ -876,6 +881,142 @@ medusaIntegrationTestRunner({
|
|||||||
expect(fullOrder.payment_collections[0].captured_amount).toBe(3000)
|
expect(fullOrder.payment_collections[0].captured_amount).toBe(3000)
|
||||||
expect(fullOrder.payment_collections[0].status).toBe("completed")
|
expect(fullOrder.payment_collections[0].status).toBe("completed")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should clear events when complete cart fails after emitting events", async () => {
|
||||||
|
const salesChannel = await scModuleService.createSalesChannels({
|
||||||
|
name: "Webshop",
|
||||||
|
})
|
||||||
|
|
||||||
|
const location = await stockLocationModule.createStockLocations({
|
||||||
|
name: "Warehouse",
|
||||||
|
})
|
||||||
|
|
||||||
|
const region = await regionModuleService.createRegions({
|
||||||
|
name: "US",
|
||||||
|
currency_code: "usd",
|
||||||
|
})
|
||||||
|
|
||||||
|
let cart = await cartModuleService.createCarts({
|
||||||
|
currency_code: "usd",
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
region_id: region.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
await remoteLink.create([
|
||||||
|
{
|
||||||
|
[Modules.SALES_CHANNEL]: {
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
},
|
||||||
|
[Modules.STOCK_LOCATION]: {
|
||||||
|
stock_location_id: location.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
cart = await cartModuleService.retrieveCart(cart.id, {
|
||||||
|
select: ["id", "region_id", "currency_code", "sales_channel_id"],
|
||||||
|
})
|
||||||
|
|
||||||
|
await addToCartWorkflow(appContainer).run({
|
||||||
|
input: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Test item",
|
||||||
|
subtitle: "Test subtitle",
|
||||||
|
thumbnail: "some-url",
|
||||||
|
requires_shipping: false,
|
||||||
|
is_discountable: false,
|
||||||
|
is_tax_inclusive: false,
|
||||||
|
unit_price: 3000,
|
||||||
|
metadata: {
|
||||||
|
foo: "bar",
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "zero price item",
|
||||||
|
subtitle: "zero price item",
|
||||||
|
thumbnail: "some-url",
|
||||||
|
requires_shipping: false,
|
||||||
|
is_discountable: false,
|
||||||
|
is_tax_inclusive: false,
|
||||||
|
unit_price: 0,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cart_id: cart.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
cart = await cartModuleService.retrieveCart(cart.id, {
|
||||||
|
relations: ["items"],
|
||||||
|
})
|
||||||
|
|
||||||
|
await createPaymentCollectionForCartWorkflow(appContainer).run({
|
||||||
|
input: {
|
||||||
|
cart_id: cart.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const [paymentCollection] =
|
||||||
|
await paymentModule.listPaymentCollections({})
|
||||||
|
|
||||||
|
await createPaymentSessionsWorkflow(appContainer).run({
|
||||||
|
input: {
|
||||||
|
payment_collection_id: paymentCollection.id,
|
||||||
|
provider_id: "pp_system_default",
|
||||||
|
context: {},
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let grouppedEventBefore: Message[] = []
|
||||||
|
let eventGroupId!: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register order.placed listener to trigger the event group
|
||||||
|
* registration and be able to check the event group during
|
||||||
|
* the workflow execution against it after compensation
|
||||||
|
*/
|
||||||
|
|
||||||
|
eventBus.subscribe("order.placed", async () => {
|
||||||
|
// noop
|
||||||
|
})
|
||||||
|
|
||||||
|
const workflow = completeCartWorkflow(appContainer)
|
||||||
|
|
||||||
|
workflow.addAction("throw", {
|
||||||
|
invoke: async function failStep({ context }) {
|
||||||
|
eventGroupId = context!.eventGroupId!
|
||||||
|
grouppedEventBefore = (
|
||||||
|
(eventBus as any).groupedEventsMap_ as Map<string, any>
|
||||||
|
).get(context!.eventGroupId!)
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Failed to do something before ending complete cart workflow`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { errors } = await workflow.run({
|
||||||
|
input: {
|
||||||
|
id: cart.id,
|
||||||
|
},
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const grouppedEventAfter =
|
||||||
|
((eventBus as any).groupedEventsMap_ as Map<string, any>).get(
|
||||||
|
eventGroupId
|
||||||
|
) ?? []
|
||||||
|
|
||||||
|
expect(grouppedEventBefore).toHaveLength(1)
|
||||||
|
expect(grouppedEventAfter).toHaveLength(0) // events have been compensated
|
||||||
|
|
||||||
|
expect(errors[0].error.message).toBe(
|
||||||
|
"Failed to do something before ending complete cart workflow"
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
import { Modules } from "@medusajs/framework/utils"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import {
|
import {
|
||||||
StepExecutionContext,
|
StepExecutionContext,
|
||||||
|
StepResponse,
|
||||||
createStep,
|
createStep,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
|
||||||
@@ -85,6 +86,25 @@ export const emitEventStep = createStep(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await eventBus.emit(message)
|
await eventBus.emit(message)
|
||||||
|
|
||||||
|
return new StepResponse({
|
||||||
|
eventGroupId: context.eventGroupId,
|
||||||
|
eventName: input.eventName,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
async (data: void) => {}
|
async (data, context) => {
|
||||||
|
if (!data || !data?.eventGroupId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { container } = context
|
||||||
|
|
||||||
|
const eventBus: IEventBusModuleService = container.resolve(
|
||||||
|
Modules.EVENT_BUS
|
||||||
|
)
|
||||||
|
|
||||||
|
await eventBus.clearGroupedEvents(data!.eventGroupId, {
|
||||||
|
eventNames: [data!.eventName],
|
||||||
|
})
|
||||||
|
}
|
||||||
)
|
)
|
||||||
@@ -73,9 +73,17 @@ export interface IEventBusModuleService {
|
|||||||
* where you need to explicitly group, release and clear events upon lifecycle events of a transaction.
|
* where you need to explicitly group, release and clear events upon lifecycle events of a transaction.
|
||||||
*
|
*
|
||||||
* @param eventGroupId - The ID of the event group.
|
* @param eventGroupId - The ID of the event group.
|
||||||
|
* @param options - Additional options for the event.
|
||||||
|
* @param options.eventNames - The names of the events to clear. If not provided, The group will
|
||||||
|
* be entirely cleared.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* await eventModuleService.clearGroupedEvents("group_123")
|
* await eventModuleService.clearGroupedEvents("group_123")
|
||||||
*/
|
*/
|
||||||
clearGroupedEvents(eventGroupId: string): Promise<void>
|
clearGroupedEvents(
|
||||||
|
eventGroupId: string,
|
||||||
|
options?: {
|
||||||
|
eventNames?: string[]
|
||||||
|
}
|
||||||
|
): Promise<void>
|
||||||
}
|
}
|
||||||
@@ -38,8 +38,16 @@ export abstract class AbstractEventBusModuleService
|
|||||||
*/
|
*/
|
||||||
// Given a eventGroupId, all the grouped events will be released
|
// Given a eventGroupId, all the grouped events will be released
|
||||||
abstract releaseGroupedEvents(eventGroupId: string): Promise<void>
|
abstract releaseGroupedEvents(eventGroupId: string): Promise<void>
|
||||||
// Given a eventGroupId, all the grouped events will be cleared
|
|
||||||
abstract clearGroupedEvents(eventGroupId: string): Promise<void>
|
// Given a eventGroupId, all the grouped events will be cleared unless eventNames are provided
|
||||||
|
// If eventNames are provided, only the events that match the eventNames will be cleared from the
|
||||||
|
// group
|
||||||
|
abstract clearGroupedEvents(
|
||||||
|
eventGroupId: string,
|
||||||
|
options?: {
|
||||||
|
eventNames?: string[]
|
||||||
|
}
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
protected storeSubscribers({
|
protected storeSubscribers({
|
||||||
event,
|
event,
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import {
|
||||||
|
CommonEvents,
|
||||||
|
composeMessage,
|
||||||
|
Modules,
|
||||||
|
} from "@medusajs/framework/utils"
|
||||||
|
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
|
import { IEventBusModuleService } from "@medusajs/types"
|
||||||
|
|
||||||
|
moduleIntegrationTestRunner<IEventBusModuleService>({
|
||||||
|
moduleName: Modules.EVENT_BUS,
|
||||||
|
testSuite: ({ service: eventBus }) => {
|
||||||
|
describe("Event Bus Local Service", () => {
|
||||||
|
it("should emit an event", async () => {
|
||||||
|
const subscriber = jest.fn()
|
||||||
|
eventBus.subscribe("test", subscriber)
|
||||||
|
|
||||||
|
await eventBus.emit(
|
||||||
|
composeMessage("test", {
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
action: CommonEvents.CREATED,
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
action: "created",
|
||||||
|
},
|
||||||
|
name: "test",
|
||||||
|
})
|
||||||
|
|
||||||
|
eventBus.unsubscribe("test", subscriber)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should release grouped events", async () => {
|
||||||
|
const subscriber = jest.fn()
|
||||||
|
eventBus.subscribe("test", subscriber)
|
||||||
|
|
||||||
|
await eventBus.emit(
|
||||||
|
composeMessage("test", {
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
eventGroupId: "123",
|
||||||
|
},
|
||||||
|
action: CommonEvents.CREATED,
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
await eventBus.releaseGroupedEvents("123")
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
source: "test",
|
||||||
|
eventGroupId: "123",
|
||||||
|
object: "test",
|
||||||
|
action: "created",
|
||||||
|
},
|
||||||
|
name: "test",
|
||||||
|
})
|
||||||
|
|
||||||
|
eventBus.unsubscribe("test", subscriber)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should clear grouped events", async () => {
|
||||||
|
const subscriber = jest.fn()
|
||||||
|
eventBus.subscribe("test", subscriber)
|
||||||
|
|
||||||
|
await eventBus.emit(
|
||||||
|
composeMessage("test", {
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
eventGroupId: "123",
|
||||||
|
},
|
||||||
|
action: CommonEvents.CREATED,
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
await eventBus.clearGroupedEvents("123")
|
||||||
|
await eventBus.releaseGroupedEvents("123")
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
eventBus.unsubscribe("test", subscriber)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should clear grouped events with event names", async () => {
|
||||||
|
const subscriber = jest.fn()
|
||||||
|
eventBus.subscribe("test", subscriber)
|
||||||
|
|
||||||
|
await eventBus.emit(
|
||||||
|
composeMessage("test", {
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
eventGroupId: "123",
|
||||||
|
},
|
||||||
|
action: CommonEvents.CREATED,
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await eventBus.clearGroupedEvents("123", {
|
||||||
|
eventNames: ["test"],
|
||||||
|
})
|
||||||
|
|
||||||
|
await eventBus.releaseGroupedEvents("123")
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
eventBus.unsubscribe("test", subscriber)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -33,7 +33,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "tsc --build --watch",
|
"watch": "tsc --build --watch",
|
||||||
"build": "rimraf dist && tsc --build",
|
"build": "rimraf dist && tsc --build",
|
||||||
"test": "jest --passWithNoTests"
|
"test": "jest --passWithNoTests",
|
||||||
|
"test:integration": "jest --no-cache --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage -- integration-tests/__tests__/**/*.spec.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ulid": "^2.3.0"
|
"ulid": "^2.3.0"
|
||||||
|
|||||||
@@ -130,8 +130,19 @@ export default class LocalEventBusService extends AbstractEventBusModuleService
|
|||||||
await this.clearGroupedEvents(eventGroupId)
|
await this.clearGroupedEvents(eventGroupId)
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearGroupedEvents(eventGroupId: string) {
|
async clearGroupedEvents(
|
||||||
this.groupedEventsMap_.delete(eventGroupId)
|
eventGroupId: string,
|
||||||
|
{ eventNames }: { eventNames?: string[] } = {}
|
||||||
|
) {
|
||||||
|
if (eventNames?.length) {
|
||||||
|
const groupedEvents = this.groupedEventsMap_.get(eventGroupId) || []
|
||||||
|
const eventsToKeep = groupedEvents.filter(
|
||||||
|
(event) => !eventNames!.includes(event.name)
|
||||||
|
)
|
||||||
|
this.groupedEventsMap_.set(eventGroupId, eventsToKeep)
|
||||||
|
} else {
|
||||||
|
this.groupedEventsMap_.delete(eventGroupId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(
|
subscribe(
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import {
|
||||||
|
CommonEvents,
|
||||||
|
composeMessage,
|
||||||
|
Modules,
|
||||||
|
} from "@medusajs/framework/utils"
|
||||||
|
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
|
import { IEventBusModuleService } from "@medusajs/types"
|
||||||
|
|
||||||
|
moduleIntegrationTestRunner<IEventBusModuleService>({
|
||||||
|
moduleName: Modules.EVENT_BUS,
|
||||||
|
moduleOptions: {
|
||||||
|
redis: {
|
||||||
|
host: "localhost",
|
||||||
|
port: 6379,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testSuite: ({ service: eventBus }) => {
|
||||||
|
describe("Event Bus Redis Service", () => {
|
||||||
|
it("should emit an event", async () => {
|
||||||
|
const subscriber = jest.fn()
|
||||||
|
eventBus.subscribe("test", subscriber)
|
||||||
|
|
||||||
|
await eventBus.emit(
|
||||||
|
composeMessage("test", {
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
action: CommonEvents.CREATED,
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
action: "created",
|
||||||
|
},
|
||||||
|
name: "test",
|
||||||
|
})
|
||||||
|
|
||||||
|
eventBus.unsubscribe("test", subscriber)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should release grouped events", async () => {
|
||||||
|
const subscriber = jest.fn()
|
||||||
|
eventBus.subscribe("test", subscriber)
|
||||||
|
|
||||||
|
await eventBus.emit(
|
||||||
|
composeMessage("test", {
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
eventGroupId: "123",
|
||||||
|
},
|
||||||
|
action: CommonEvents.CREATED,
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
await eventBus.releaseGroupedEvents("123")
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
source: "test",
|
||||||
|
eventGroupId: "123",
|
||||||
|
object: "test",
|
||||||
|
action: "created",
|
||||||
|
},
|
||||||
|
name: "test",
|
||||||
|
})
|
||||||
|
|
||||||
|
eventBus.unsubscribe("test", subscriber)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should clear grouped events", async () => {
|
||||||
|
const subscriber = jest.fn()
|
||||||
|
eventBus.subscribe("test", subscriber)
|
||||||
|
|
||||||
|
await eventBus.emit(
|
||||||
|
composeMessage("test", {
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
eventGroupId: "123",
|
||||||
|
},
|
||||||
|
action: CommonEvents.CREATED,
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
await eventBus.clearGroupedEvents("123")
|
||||||
|
await eventBus.releaseGroupedEvents("123")
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
eventBus.unsubscribe("test", subscriber)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should clear grouped events with event names", async () => {
|
||||||
|
const subscriber = jest.fn()
|
||||||
|
eventBus.subscribe("test", subscriber)
|
||||||
|
|
||||||
|
await eventBus.emit(
|
||||||
|
composeMessage("test", {
|
||||||
|
data: {
|
||||||
|
test: "test",
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
eventGroupId: "123",
|
||||||
|
},
|
||||||
|
action: CommonEvents.CREATED,
|
||||||
|
source: "test",
|
||||||
|
object: "test",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await eventBus.clearGroupedEvents("123", {
|
||||||
|
eventNames: ["test"],
|
||||||
|
})
|
||||||
|
|
||||||
|
await eventBus.releaseGroupedEvents("123")
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
eventBus.unsubscribe("test", subscriber)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -33,7 +33,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "tsc --build --watch",
|
"watch": "tsc --build --watch",
|
||||||
"build": "rimraf dist && tsc --build",
|
"build": "rimraf dist && tsc --build",
|
||||||
"test": "jest --silent --bail --maxWorkers=50% --forceExit"
|
"test": "jest --silent --bail --maxWorkers=50% --forceExit",
|
||||||
|
"test:integration": "jest --no-cache --maxWorkers=50% --bail --detectOpenHandles --forceExit --logHeapUsage -- integration-tests/__tests__/**/*.spec.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bullmq": "5.13.0",
|
"bullmq": "5.13.0",
|
||||||
|
|||||||
@@ -218,11 +218,49 @@ export default class RedisEventBusService extends AbstractEventBusModuleService
|
|||||||
await this.clearGroupedEvents(eventGroupId)
|
await this.clearGroupedEvents(eventGroupId)
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearGroupedEvents(eventGroupId: string) {
|
async clearGroupedEvents(
|
||||||
|
eventGroupId: string,
|
||||||
|
{
|
||||||
|
eventNames,
|
||||||
|
}: {
|
||||||
|
eventNames?: string[]
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
if (!eventGroupId) {
|
if (!eventGroupId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventNames?.length) {
|
||||||
|
/**
|
||||||
|
* If any event names are provided, we keep all events except the ones that match the event
|
||||||
|
* names. which allow to partially clear an event group.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const eventsToKeep = await this.eventBusRedisConnection_
|
||||||
|
.lrange(`staging:${eventGroupId}`, 0, -1)
|
||||||
|
.then((result) => {
|
||||||
|
return result
|
||||||
|
.map((jsonString) => JSON.parse(jsonString))
|
||||||
|
.filter((event) => !eventNames.includes(event.name))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a pipeline
|
||||||
|
const pipeline = this.eventBusRedisConnection_.pipeline()
|
||||||
|
|
||||||
|
// Empty the current list
|
||||||
|
pipeline.del(`staging:${eventGroupId}`)
|
||||||
|
|
||||||
|
// Add the remaining events to the list
|
||||||
|
pipeline.rpush(
|
||||||
|
`staging:${eventGroupId}`,
|
||||||
|
...eventsToKeep.map((event) => JSON.stringify(event))
|
||||||
|
)
|
||||||
|
|
||||||
|
await pipeline.exec()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await this.eventBusRedisConnection_.unlink(`staging:${eventGroupId}`)
|
await this.eventBusRedisConnection_.unlink(`staging:${eventGroupId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user