feat(medusa): Bulk emit events (#3407)

This commit is contained in:
Adrien de Peretti
2023-03-13 15:28:51 +01:00
committed by GitHub
parent 601d20e7ab
commit f0a1355feb
14 changed files with 404 additions and 166 deletions
+7
View File
@@ -0,0 +1,7 @@
---
"@medusajs/inventory": patch
"medusa-test-utils": patch
"@medusajs/medusa": patch
---
feat(medusa): Bulk emit events
@@ -3,7 +3,7 @@ const path = require("path")
const { bootstrapApp } = require("../../../helpers/bootstrap-app") const { bootstrapApp } = require("../../../helpers/bootstrap-app")
const { initDb, useDb } = require("../../../helpers/use-db") const { initDb, useDb } = require("../../../helpers/use-db")
jest.setTimeout(30000) jest.setTimeout(50000)
describe("Inventory Module", () => { describe("Inventory Module", () => {
let appContainer let appContainer
@@ -1,17 +1,16 @@
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils" import { isDefined, MedusaError } from "medusa-core-utils"
import { import {
FindConfig,
buildQuery, buildQuery,
IEventBusService,
FilterableReservationItemProps,
CreateReservationItemInput, CreateReservationItemInput,
FilterableReservationItemProps,
FindConfig,
IEventBusService,
TransactionBaseService, TransactionBaseService,
UpdateReservationItemInput, UpdateReservationItemInput,
} from "@medusajs/medusa" } from "@medusajs/medusa"
import { ReservationItem } from "../models" import { ReservationItem } from "../models"
import { CONNECTION_NAME } from "../config"
import { InventoryLevelService } from "." import { InventoryLevelService } from "."
type InjectedDependencies = { type InjectedDependencies = {
@@ -278,10 +277,12 @@ export default class ReservationItemService extends TransactionBaseService {
item.quantity * -1 item.quantity * -1
), ),
]) ])
})
await this.eventBusService_.emit(ReservationItemService.Events.DELETED, { await this.eventBusService_
id: reservationItemId, .withTransaction(manager)
.emit(ReservationItemService.Events.DELETED, {
id: reservationItemId,
})
}) })
} }
} }
@@ -12,6 +12,8 @@ class MockRepo {
save, save,
findAndCount, findAndCount,
del, del,
count,
insertBulk
}) { }) {
this.create_ = create; this.create_ = create;
this.update_ = update; this.update_ = update;
@@ -25,12 +27,19 @@ class MockRepo {
this.save_ = save; this.save_ = save;
this.findAndCount_ = findAndCount; this.findAndCount_ = findAndCount;
this.findOneWithRelations_ = findOneWithRelations; this.findOneWithRelations_ = findOneWithRelations;
this.insertBulk_ = insertBulk;
} }
setFindOne(fn) { setFindOne(fn) {
this.findOne_ = fn; this.findOne_ = fn;
} }
insertBulk = jest.fn().mockImplementation((...args) => {
if (this.insertBulk_) {
return this.insertBulk_(...args)
}
return {}
})
create = jest.fn().mockImplementation((...args) => { create = jest.fn().mockImplementation((...args) => {
if (this.create_) { if (this.create_) {
return this.create_(...args); return this.create_(...args);
+25 -2
View File
@@ -1,5 +1,28 @@
import { EntityRepository, Repository } from "typeorm" import { EntityRepository, Repository } from "typeorm"
import { StagedJob } from "../models/staged-job" import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
import { StagedJob } from "../models"
import { rowSqlResultsToEntityTransformer } from "../utils"
@EntityRepository(StagedJob) @EntityRepository(StagedJob)
export class StagedJobRepository extends Repository<StagedJob> {} export class StagedJobRepository extends Repository<StagedJob> {
async insertBulk(jobToCreates: QueryDeepPartialEntity<StagedJob>[]) {
const queryBuilder = this.createQueryBuilder()
.insert()
.into(StagedJob)
.values(jobToCreates)
// TODO: remove if statement once this issue is resolved https://github.com/typeorm/typeorm/issues/9850
if (!queryBuilder.connection.driver.isReturningSqlSupported("insert")) {
const rawStagedJobs = await queryBuilder.execute()
return rawStagedJobs.generatedMaps
}
const rawStagedJobs = await queryBuilder.returning("*").execute()
return rowSqlResultsToEntityTransformer(
rawStagedJobs.raw,
queryBuilder,
this.queryRunner!
)
}
}
@@ -114,79 +114,174 @@ describe("EventBusService", () => {
}) })
describe("emit", () => { describe("emit", () => {
let eventBus const eventName = "eventName"
let job const defaultOptions = {
attempts: 1,
removeOnComplete: true,
}
const data = { hi: "1234" }
const bulkData = [{ hi: "1234" }, { hi: "12345" }]
const mockManager = MockManager
describe("successfully adds job to queue", () => { describe("successfully adds job to queue", () => {
beforeAll(() => { let eventBus
jest.resetAllMocks() let stagedJobRepository
const stagedJobRepository = MockRepository({
find: () => Promise.resolve([]), beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
}) })
eventBus = new EventBusService({ eventBus = new EventBusService({
logger: loggerMock, logger: loggerMock,
manager: MockManager, manager: mockManager,
stagedJobRepository, stagedJobRepository,
}) })
eventBus.queue_.add.mockImplementationOnce(() => "hi") eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
job = eventBus.emit("eventName", { hi: "1234" })
}) })
afterAll(async () => {
afterEach(async () => {
await eventBus.stopEnqueuer() await eventBus.stopEnqueuer()
jest.clearAllMocks()
}) })
it("calls queue.add", () => { it("calls queue.addBulk", async () => {
expect(eventBus.queue_.add).toHaveBeenCalled() await eventBus.emit(eventName, data)
expect(eventBus.queue_.addBulk).toHaveBeenCalled()
expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
{
data: {
data,
eventName,
},
opts: defaultOptions,
},
])
})
it("calls stagedJob repository insertBulk", async () => {
await eventBus.withTransaction(mockManager).emit(eventName, data)
expect(stagedJobRepository.create).toHaveBeenCalled()
expect(stagedJobRepository.create).toHaveBeenCalledWith({
event_name: eventName,
data: data,
options: defaultOptions,
})
expect(stagedJobRepository.insertBulk).toHaveBeenCalled()
expect(stagedJobRepository.insertBulk).toHaveBeenCalledWith([
{
event_name: eventName,
data,
options: defaultOptions,
},
])
}) })
}) })
describe("successfully adds job to queue with local options", () => { describe("successfully adds jobs in bulk to queue", () => {
beforeAll(() => { let eventBus
jest.resetAllMocks() let stagedJobRepository
const stagedJobRepository = MockRepository({
find: () => Promise.resolve([]), beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
}) })
eventBus = new EventBusService({ eventBus = new EventBusService({
logger: loggerMock, logger: loggerMock,
manager: MockManager, manager: mockManager,
stagedJobRepository, stagedJobRepository,
}) })
eventBus.queue_.add.mockImplementationOnce(() => "hi") eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
job = eventBus.emit(
"eventName",
{ hi: "1234" },
{ removeOnComplete: 100 }
)
}) })
afterAll(async () => {
afterEach(async () => {
jest.clearAllMocks()
await eventBus.stopEnqueuer() await eventBus.stopEnqueuer()
}) })
it("calls queue.add", () => { it("calls queue.addBulk", async () => {
expect(eventBus.queue_.add).toHaveBeenCalled() await eventBus.emit([
expect(eventBus.queue_.add).toHaveBeenCalledWith( { eventName, data: bulkData[0] },
{ eventName: "eventName", data: { hi: "1234" } }, { eventName, data: bulkData[1] },
{ removeOnComplete: 100 } ])
)
expect(eventBus.queue_.addBulk).toHaveBeenCalledTimes(1)
expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
{
data: {
data: bulkData[0],
eventName,
},
opts: defaultOptions,
},
{
data: {
data: bulkData[1],
eventName,
},
opts: defaultOptions,
},
])
})
it("calls stagedJob repository insertBulk", async () => {
await eventBus.withTransaction(mockManager).emit([
{ eventName, data: bulkData[0] },
{ eventName, data: bulkData[1] },
])
expect(stagedJobRepository.create).toHaveBeenCalledTimes(2)
expect(stagedJobRepository.create).toHaveBeenNthCalledWith(1, {
data: bulkData[0],
event_name: eventName,
options: defaultOptions,
})
expect(stagedJobRepository.create).toHaveBeenNthCalledWith(2, {
data: bulkData[1],
event_name: eventName,
options: defaultOptions,
})
expect(stagedJobRepository.insertBulk).toHaveBeenCalledTimes(1)
expect(stagedJobRepository.insertBulk).toHaveBeenCalledWith([
{
data: bulkData[0],
event_name: eventName,
options: defaultOptions,
},
{
data: bulkData[1],
event_name: eventName,
options: defaultOptions,
},
])
}) })
}) })
describe("successfully adds job to queue with global options", () => { describe("successfully adds job to queue with global options", () => {
beforeAll(() => { let eventBus
jest.resetAllMocks() let stagedJobRepository
const stagedJobRepository = MockRepository({
find: () => Promise.resolve([]), beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
}) })
eventBus = new EventBusService( eventBus = new EventBusService(
{ {
logger: loggerMock, logger: loggerMock,
manager: MockManager, manager: mockManager,
stagedJobRepository, stagedJobRepository,
}, },
{ {
@@ -194,58 +289,78 @@ describe("EventBusService", () => {
} }
) )
eventBus.queue_.add.mockImplementationOnce(() => "hi") eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
job = eventBus.emit("eventName", { hi: "1234" }) eventBus.emit(eventName, data)
}) })
afterAll(async () => {
afterEach(async () => {
jest.clearAllMocks()
await eventBus.stopEnqueuer() await eventBus.stopEnqueuer()
}) })
it("calls queue.add", () => { it("calls queue.addBulk", () => {
expect(eventBus.queue_.add).toHaveBeenCalled() expect(eventBus.queue_.addBulk).toHaveBeenCalled()
expect(eventBus.queue_.add).toHaveBeenCalledWith( expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
{ eventName: "eventName", data: { hi: "1234" } }, {
{ removeOnComplete: 10, attempts: 1 } data: {
) data,
eventName,
},
opts: { removeOnComplete: 10, attempts: 1 },
},
])
}) })
}) })
describe("successfully adds job to queue with default options", () => { describe("successfully adds job to queue with default options", () => {
beforeAll(() => { let eventBus
jest.resetAllMocks() let stagedJobRepository
const stagedJobRepository = MockRepository({
find: () => Promise.resolve([]), beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
}) })
eventBus = new EventBusService({ eventBus = new EventBusService({
logger: loggerMock, logger: loggerMock,
manager: MockManager, manager: mockManager,
stagedJobRepository, stagedJobRepository,
}) })
eventBus.queue_.add.mockImplementationOnce(() => "hi") eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
job = eventBus.emit("eventName", { hi: "1234" }) eventBus.emit(eventName, data)
}) })
afterAll(async () => {
afterEach(async () => {
jest.clearAllMocks()
await eventBus.stopEnqueuer() await eventBus.stopEnqueuer()
}) })
it("calls queue.add", () => { it("calls queue.addBulk", () => {
expect(eventBus.queue_.add).toHaveBeenCalled() expect(eventBus.queue_.addBulk).toHaveBeenCalled()
expect(eventBus.queue_.add).toHaveBeenCalledWith( expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
{ eventName: "eventName", data: { hi: "1234" } }, {
{ removeOnComplete: true, attempts: 1 } data: {
) data,
eventName,
},
opts: { removeOnComplete: true, attempts: 1 },
},
])
}) })
}) })
describe("successfully adds job to queue with local options and global options merged", () => { describe("successfully adds job to queue with local options and global options merged", () => {
beforeAll(() => { let eventBus
jest.resetAllMocks() let stagedJobRepository
const stagedJobRepository = MockRepository({
find: () => Promise.resolve([]), beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
}) })
eventBus = new EventBusService( eventBus = new EventBusService(
@@ -259,29 +374,36 @@ describe("EventBusService", () => {
} }
) )
eventBus.queue_.add.mockImplementationOnce(() => "hi") eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
job = eventBus.emit( eventBus.emit(eventName, data, {
"eventName", attempts: 10,
{ hi: "1234" }, delay: 1000,
{ attempts: 10, delay: 1000, backoff: { type: "exponential" } } backoff: { type: "exponential" },
) })
}) })
afterAll(async () => {
afterEach(async () => {
jest.clearAllMocks()
await eventBus.stopEnqueuer() await eventBus.stopEnqueuer()
}) })
it("calls queue.add", () => { it("calls queue.add", () => {
expect(eventBus.queue_.add).toHaveBeenCalled() expect(eventBus.queue_.addBulk).toHaveBeenCalled()
expect(eventBus.queue_.add).toHaveBeenCalledWith( expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
{ eventName: "eventName", data: { hi: "1234" } },
{ {
removeOnComplete: 10, // global option data: {
attempts: 10, // local option data,
delay: 1000, // local option eventName,
backoff: { type: "exponential" }, // local option },
} opts: {
) removeOnComplete: 10, // global option
attempts: 10, // local option
delay: 1000, // local option
backoff: { type: "exponential" }, // local option
},
},
])
}) })
}) })
}) })
+11 -13
View File
@@ -1,8 +1,8 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils" import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import OrderService from "../order" import OrderService from "../order"
import { ProductVariantInventoryServiceMock } from "../__mocks__/product-variant-inventory"
import { LineItemServiceMock } from "../__mocks__/line-item" import { LineItemServiceMock } from "../__mocks__/line-item"
import { newTotalsServiceMock } from "../__mocks__/new-totals" import { newTotalsServiceMock } from "../__mocks__/new-totals"
import { ProductVariantInventoryServiceMock } from "../__mocks__/product-variant-inventory"
import { taxProviderServiceMock } from "../__mocks__/tax-provider" import { taxProviderServiceMock } from "../__mocks__/tax-provider"
describe("OrderService", () => { describe("OrderService", () => {
@@ -1072,10 +1072,15 @@ describe("OrderService", () => {
{ no_notification: input } { no_notification: input }
) )
expect(eventBusService.emit).toHaveBeenCalledWith(expect.any(String), { expect(eventBusService.emit).toHaveBeenCalledWith([
id: expect.any(String), {
no_notification: expected, eventName: expect.any(String),
}) data: {
id: expect.any(String),
no_notification: expected,
},
},
])
} }
) )
}) })
@@ -1246,17 +1251,10 @@ describe("OrderService", () => {
save: jest.fn().mockImplementation((f) => f), save: jest.fn().mockImplementation((f) => f),
}) })
const eventBus = {
emit: () =>
Promise.resolve({
finished: () => Promise.resolve({}),
}),
}
const orderService = new OrderService({ const orderService = new OrderService({
manager: MockManager, manager: MockManager,
orderRepository: orderRepo, orderRepository: orderRepo,
eventBusService: eventBus, eventBusService: eventBusService,
}) })
beforeEach(async () => { beforeEach(async () => {
+8 -6
View File
@@ -638,15 +638,17 @@ export default class ClaimService extends TransactionBaseService {
) )
const claimOrder = await claimRepo.save(claim) const claimOrder = await claimRepo.save(claim)
const eventBusTx = this.eventBus_.withTransaction(transactionManager) const eventsToEmit = fulfillments.map((fulfillment) => ({
eventName: ClaimService.Events.FULFILLMENT_CREATED,
for (const fulfillment of fulfillments) { data: {
await eventBusTx.emit(ClaimService.Events.FULFILLMENT_CREATED, {
id: id, id: id,
fulfillment_id: fulfillment.id, fulfillment_id: fulfillment.id,
no_notification: claim.no_notification, no_notification: claim.no_notification,
}) },
} }))
await this.eventBus_
.withTransaction(transactionManager)
.emit(eventsToEmit)
return claimOrder return claimOrder
} }
+5 -3
View File
@@ -122,9 +122,11 @@ export default class CurrencyService extends TransactionBaseService {
) )
await currencyRepo.save(currency) await currencyRepo.save(currency)
await this.eventBusService_.emit(CurrencyService.Events.UPDATED, { await this.eventBusService_
code, .withTransaction(transactionManager)
}) .emit(CurrencyService.Events.UPDATED, {
code,
})
return currency return currency
}) })
+74 -35
View File
@@ -1,11 +1,12 @@
import Bull, { JobOptions } from "bull" import Bull, { JobOptions } from "bull"
import Redis from "ioredis" import Redis from "ioredis"
import { isDefined } from "medusa-core-utils" import { DeepPartial, EntityManager, In } from "typeorm"
import { EntityManager } from "typeorm" import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
import { ulid } from "ulid" import { ulid } from "ulid"
import { StagedJob } from "../models" import { StagedJob } from "../models"
import { StagedJobRepository } from "../repositories/staged-job" import { StagedJobRepository } from "../repositories/staged-job"
import { ConfigModule, Logger } from "../types/global" import { ConfigModule, Logger } from "../types/global"
import { isString } from "../utils"
import { sleep } from "../utils/sleep" import { sleep } from "../utils/sleep"
import JobSchedulerService, { CreateJobOptions } from "./job-scheduler" import JobSchedulerService, { CreateJobOptions } from "./job-scheduler"
@@ -49,6 +50,12 @@ export type EmitOptions = {
} }
} & JobOptions } & JobOptions
export type EmitData<T = unknown> = {
eventName: string
data: T
opts?: Record<string, unknown> & EmitOptions
}
/** /**
* Can keep track of multiple subscribers to different events and run the * Can keep track of multiple subscribers to different events and run the
* subscribers when events happen. Events will run asynchronously. * subscribers when events happen. Events will run asynchronously.
@@ -213,6 +220,13 @@ export default class EventBusService {
return this return this
} }
/**
* Calls all subscribers when an event occurs.
* @param data - The data to use to process the events
* @return the jobs from our queue
*/
async emit<T>(data: EmitData<T>[]): Promise<StagedJob[] | void>
/** /**
* Calls all subscribers when an event occurs. * Calls all subscribers when an event occurs.
* @param {string} eventName - the name of the event to be process. * @param {string} eventName - the name of the event to be process.
@@ -223,29 +237,49 @@ export default class EventBusService {
async emit<T>( async emit<T>(
eventName: string, eventName: string,
data: T, data: T,
options: Record<string, unknown> & EmitOptions = { attempts: 1 } options?: Record<string, unknown> & EmitOptions
): Promise<StagedJob | void> { ): Promise<StagedJob | void>
async emit<
T,
TInput extends string | EmitData<T>[] = string,
TResult = TInput extends EmitData<T>[] ? StagedJob[] : StagedJob
>(
eventNameOrData: TInput,
data?: T,
options: Record<string, unknown> & EmitOptions = {}
): Promise<TResult | void> {
const globalEventOptions = this.config_?.projectConfig?.event_options ?? {} const globalEventOptions = this.config_?.projectConfig?.event_options ?? {}
const isBulkEmit = !isString(eventNameOrData)
const events = isBulkEmit
? eventNameOrData.map((event) => ({
data: { eventName: event.eventName, data: event.data },
opts: event.opts,
}))
: [
{
data: { eventName: eventNameOrData, data },
opts: options,
},
]
// The order of precedence for job options is: // The order of precedence for job options is:
// 1. local options // 1. local options
// 2. global options // 2. global options
// 3. default options // 3. default options
const opts: EmitOptions = { const defaultOptions: EmitOptions = {
removeOnComplete: true, attempts: 1, // default
...globalEventOptions, removeOnComplete: true, // default
...options, ...globalEventOptions, // global
} }
if (typeof options.attempts === "number") { for (const event of events) {
opts.attempts = options.attempts event.opts = {
if (isDefined(options.backoff)) { ...defaultOptions,
opts.backoff = options.backoff ...(event.opts ?? {}), // local
} }
} }
if (typeof options.delay === "number") {
opts.delay = options.delay
}
/** /**
* If we are in an ongoing transaction, we store the jobs in the database * If we are in an ongoing transaction, we store the jobs in the database
@@ -261,18 +295,20 @@ export default class EventBusService {
this.stagedJobRepository_ this.stagedJobRepository_
) )
const jobToCreate = { const jobsToCreate = events.map((event) => {
event_name: eventName, return stagedJobRepository.create({
data: data as unknown as Record<string, unknown>, event_name: event.data.eventName,
options: opts, data: event.data.data,
} as Partial<StagedJob> options: event.opts,
} as DeepPartial<StagedJob>) as QueryDeepPartialEntity<StagedJob>
})
const stagedJobInstance = stagedJobRepository.create(jobToCreate) const stagedJobs = await stagedJobRepository.insertBulk(jobsToCreate)
return await stagedJobRepository.save(stagedJobInstance) return (!isBulkEmit ? stagedJobs[0] : stagedJobs) as unknown as TResult
} }
this.queue_.add({ eventName, data }, opts) await this.queue_.addBulk(events)
} }
startEnqueuer(): void { startEnqueuer(): void {
@@ -298,18 +334,21 @@ export default class EventBusService {
) )
const jobs = await stagedJobRepo.find(listConfig) const jobs = await stagedJobRepo.find(listConfig)
await Promise.all( if (!jobs.length) {
jobs.map((job) => { await sleep(3000)
this.queue_ continue
.add( }
{ eventName: job.event_name, data: job.data },
{ jobId: job.id, ...job.options } const eventsData = jobs.map((job) => {
) return {
.then(async () => { data: { eventName: job.event_name, data: job.data },
await stagedJobRepo.remove(job) opts: { jobId: job.id, ...job.options },
}) }
}) })
)
await this.queue_.addBulk(eventsData).then(async () => {
return await stagedJobRepo.delete({ id: In(jobs.map((j) => j.id)) })
})
await sleep(3000) await sleep(3000)
} }
+22 -17
View File
@@ -17,14 +17,14 @@ import {
PaymentStatus, PaymentStatus,
Return, Return,
Swap, Swap,
TrackingLink, TrackingLink
} from "../models" } from "../models"
import { AddressRepository } from "../repositories/address" import { AddressRepository } from "../repositories/address"
import { OrderRepository } from "../repositories/order" import { OrderRepository } from "../repositories/order"
import { FindConfig, QuerySelector, Selector } from "../types/common" import { FindConfig, QuerySelector, Selector } from "../types/common"
import { import {
CreateFulfillmentOrder, CreateFulfillmentOrder,
FulFillmentItemType, FulFillmentItemType
} from "../types/fulfillment" } from "../types/fulfillment"
import { TotalsContext, UpdateOrderInput } from "../types/orders" import { TotalsContext, UpdateOrderInput } from "../types/orders"
import { CreateShippingMethodDto } from "../types/shipping-options" import { CreateShippingMethodDto } from "../types/shipping-options"
@@ -48,7 +48,7 @@ import {
ShippingOptionService, ShippingOptionService,
ShippingProfileService, ShippingProfileService,
TaxProviderService, TaxProviderService,
TotalsService, TotalsService
} from "." } from "."
export const ORDER_CART_ALREADY_EXISTS_ERROR = "Order from cart already exists" export const ORDER_CART_ALREADY_EXISTS_ERROR = "Order from cart already exists"
@@ -521,10 +521,12 @@ class OrderService extends TransactionBaseService {
) )
} }
await this.eventBus_.emit(OrderService.Events.COMPLETED, { await this.eventBus_
id: orderId, .withTransaction(manager)
no_notification: order.no_notification, .emit(OrderService.Events.COMPLETED, {
}) id: orderId,
no_notification: order.no_notification,
})
order.status = OrderStatus.COMPLETED order.status = OrderStatus.COMPLETED
@@ -1396,14 +1398,15 @@ class OrderService extends TransactionBaseService {
const evaluatedNoNotification = const evaluatedNoNotification =
no_notification !== undefined ? no_notification : order.no_notification no_notification !== undefined ? no_notification : order.no_notification
const eventBusTx = this.eventBus_.withTransaction(manager) const eventsToEmit = fulfillments.map((fulfillment) => ({
for (const fulfillment of fulfillments) { eventName: OrderService.Events.FULFILLMENT_CREATED,
await eventBusTx.emit(OrderService.Events.FULFILLMENT_CREATED, { data: {
id: orderId, id: orderId,
fulfillment_id: fulfillment.id, fulfillment_id: fulfillment.id,
no_notification: evaluatedNoNotification, no_notification: evaluatedNoNotification,
}) },
} }))
await this.eventBus_.withTransaction(manager).emit(eventsToEmit)
return result return result
}) })
@@ -1560,11 +1563,13 @@ class OrderService extends TransactionBaseService {
const evaluatedNoNotification = const evaluatedNoNotification =
no_notification !== undefined ? no_notification : order.no_notification no_notification !== undefined ? no_notification : order.no_notification
await this.eventBus_.emit(OrderService.Events.REFUND_CREATED, { await this.eventBus_
id: result.id, .withTransaction(manager)
refund_id: refund.id, .emit(OrderService.Events.REFUND_CREATED, {
no_notification: evaluatedNoNotification, id: result.id,
}) refund_id: refund.id,
no_notification: evaluatedNoNotification,
})
return result return result
}) })
} }
+3 -1
View File
@@ -275,7 +275,9 @@ class UserService extends TransactionBaseService {
await userRepo.softRemove(user) await userRepo.softRemove(user)
await this.eventBus_.emit(UserService.Events.DELETED, { id: user.id }) await this.eventBus_
.withTransaction(manager)
.emit(UserService.Events.DELETED, { id: user.id })
return Promise.resolve() return Promise.resolve()
}) })
+1
View File
@@ -8,3 +8,4 @@ export * from "./calculate-price-tax-amount"
export * from "./csv-cell-content-formatter" export * from "./csv-cell-content-formatter"
export * from "./exception-formatter" export * from "./exception-formatter"
export * from "./db-aware-column" export * from "./db-aware-column"
export * from "./row-sql-results-to-entity-transformer"
@@ -0,0 +1,27 @@
import { RelationIdLoader } from "typeorm/query-builder/relation-id/RelationIdLoader"
import { RawSqlResultsToEntityTransformer } from "typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer"
import { QueryBuilder, QueryRunner } from "typeorm"
export async function rowSqlResultsToEntityTransformer<T>(
rows: any[],
queryBuilder: QueryBuilder<T>,
queryRunner: QueryRunner
): Promise<T[]> {
const relationIdLoader = new RelationIdLoader(
queryBuilder.connection,
queryRunner,
queryBuilder.expressionMap.relationIdAttributes
)
const transformer = new RawSqlResultsToEntityTransformer(
queryBuilder.expressionMap,
queryBuilder.connection.driver,
[],
[],
queryRunner
)
return transformer.transform(
rows,
queryBuilder.expressionMap.mainAlias!
) as T[]
}