feat(medusa,event-bus-local,event-bus-redis): Event Bus modules (#2599)

This commit is contained in:
Oliver Windall Juhl authored and GitHub committed 2023-03-22 10:26:21 +01:00
1 parent 7408111d11
commit ef5ef9f5a2
114 files changed
+2423 -1669

No files matched your search

@@ -3,7 +3,7 @@ import { request } from "../../../../../helpers/test-request"
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
import {
defaultOrderEditFields,
defaultOrderEditRelations
defaultOrderEditRelations,
} from "../../../../../types/order-edit"
describe("GET /admin/order-edits/:id", () => {
@@ -4,22 +4,20 @@ import {
IsInt,
IsOptional,
IsString,
ValidateNested,
ValidateNested
} from "class-validator"
import {
EventBusService,
OrderService,
ReturnService,
ReturnService
} from "../../../../services"
import { Type } from "class-transformer"
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import { Order, Return } from "../../../../models"
import { OrdersReturnItem } from "../../../../types/orders"
import { FindParams } from "../../../../types/common"
import { FlagRouter } from "../../../../utils/flag-router"
import { IInventoryService } from "../../../../interfaces"
import { OrdersReturnItem } from "../../../../types/orders"
/**
* @oas [post] /admin/orders/{id}/return
@@ -1,10 +1,7 @@
import { IsNotEmpty, IsString } from "class-validator"
import { MedusaError } from "medusa-core-utils"
import {
CustomerService,
EventBusService,
OrderService,
} from "../../../../services"
import { CustomerService, OrderService } from "../../../../services"
import EventBusService from "../../../../services/event-bus"
import TokenService from "../../../../services/token"
import { TokenEvents } from "../../../../types/token"
+5 -5
View File
@@ -1,3 +1,8 @@
import {
moduleHelper,
moduleLoader,
registerModules,
} from "@medusajs/modules-sdk"
import { asValue, createContainer } from "awilix"
import express from "express"
import jwt from "jsonwebtoken"
@@ -7,11 +12,6 @@ import "reflect-metadata"
import supertest from "supertest"
import apiLoader from "../loaders/api"
import featureFlagLoader, { featureFlagRouter } from "../loaders/feature-flags"
import {
moduleLoader,
moduleHelper,
registerModules,
} from "@medusajs/modules-sdk"
import passportLoader from "../loaders/passport"
import servicesLoader from "../loaders/services"
import strategiesLoader from "../loaders/strategies"
@@ -0,0 +1,132 @@
import { EventBusTypes } from "@medusajs/types"
import { EventBusUtils } from "@medusajs/utils"
import { EntityManager } from "typeorm"
class EventBus extends EventBusUtils.AbstractEventBusModuleService {
protected manager_!: EntityManager
constructor(protected readonly container) {
super()
this.container = container
}
async emit<T>(
eventName: string,
data: T,
options: Record<string, unknown>
): Promise<void>
async emit<T>(data: EventBusTypes.EmitData<T>[]): Promise<void>
async emit<T, TInput extends string | EventBusTypes.EmitData<T>[] = string>(
eventOrData: TInput,
data?: T,
options: Record<string, unknown> = {}
): Promise<void> {
const isBulkEmit = Array.isArray(eventOrData)
const event = isBulkEmit ? eventOrData[0].eventName : eventOrData
console.log(
`[${event}] Local Event Bus installed. Emitting events has no effect.`
)
}
}
describe("AbstractEventBusService", () => {
let eventBus
describe("subscribe", () => {
beforeAll(() => {
jest.clearAllMocks()
})
beforeEach(() => {
eventBus = new EventBus({})
})
it("successfully adds subscriber", () => {
eventBus.subscribe("eventName", () => "test", {
subscriberId: "my-subscriber",
})
expect(eventBus.eventToSubscribersMap_.get("eventName").length).toEqual(1)
})
it("successfully adds multiple subscribers with explicit ids", () => {
eventBus.subscribe("eventName", () => "test", {
subscriberId: "my-subscriber-1",
})
eventBus.subscribe("eventName", () => "test", {
subscriberId: "my-subscriber-2",
})
expect(eventBus.eventToSubscribersMap_.get("eventName").length).toEqual(2)
})
it("successfully adds multiple subscribers with generates ids", () => {
eventBus.subscribe("eventName", () => "test")
eventBus.subscribe("eventName", () => "test")
expect(eventBus.eventToSubscribersMap_.get("eventName").length).toEqual(2)
})
it("throws when subscriber already exists", async () => {
expect.assertions(1)
eventBus.subscribe("eventName", () => "test", {
subscriberId: "my-subscriber",
})
try {
eventBus.subscribe("eventName", () => "new", {
subscriberId: "my-subscriber",
})
} catch (error) {
expect(error.message).toBe(
"Subscriber with id my-subscriber already exists"
)
}
})
it("throws when subscriber is not a function", async () => {
expect.assertions(1)
try {
eventBus.subscribe("eventName", "definitely-not-a-function")
} catch (error) {
expect(error.message).toBe("Subscriber must be a function")
}
})
})
describe("unsubscribe", () => {
beforeAll(() => {
jest.clearAllMocks()
})
beforeEach(() => {
eventBus = new EventBus({})
eventBus.subscribe("eventName", () => "test", { subscriberId: "test" })
})
it("successfully removes subscriber", () => {
eventBus.unsubscribe("eventName", () => "test", { subscriberId: "test" })
expect(eventBus.eventToSubscribersMap_.get("eventName").length).toEqual(0)
})
it("does nothing if subscriber does not exist", () => {
eventBus.unsubscribe("eventName", () => "non-existing")
expect(eventBus.eventToSubscribersMap_.get("eventName").length).toEqual(1)
})
it("does nothing if event has no subcribers", () => {
eventBus.unsubscribe("non-existing", () => "test")
expect(eventBus.eventToSubscribersMap_.get("eventName").length).toEqual(1)
})
})
})
@@ -1,8 +1,8 @@
import { TransactionBaseService } from "./transaction-base-service"
import { BatchJobResultError, CreateBatchJobInput } from "../types/batch-job"
import { ProductExportBatchJob } from "../strategies/batch-jobs/product/types"
import { BatchJobService } from "../services"
import { BatchJob } from "../models"
import { BatchJobService } from "../services"
import { ProductExportBatchJob } from "../strategies/batch-jobs/product/types"
import { BatchJobResultError, CreateBatchJobInput } from "../types/batch-job"
import { TransactionBaseService } from "./transaction-base-service"
export interface IBatchJobStrategy extends TransactionBaseService {
/**
+8 -8
View File
@@ -1,14 +1,14 @@
export * from "./tax-calculation-strategy"
export * from "./cart-completion-strategy"
export * from "./tax-service"
export * from "./transaction-base-service"
export * from "./batch-job-strategy"
export * from "./cart-completion-strategy"
export * from "./file-service"
export * from "./notification-service"
export * from "./price-selection-strategy"
export * from "./models/base-entity"
export * from "./models/soft-deletable-entity"
export * from "./search-service"
export * from "./payment-service"
export * from "./notification-service"
export * from "./payment-processor"
export * from "./payment-service"
export * from "./price-selection-strategy"
export * from "./search-service"
export * from "./services"
export * from "./tax-calculation-strategy"
export * from "./tax-service"
export * from "./transaction-base-service"
@@ -1,6 +0,0 @@
import { EntityManager } from "typeorm"
export interface IEventBusService {
emit(event: string, data: any): Promise<void>
withTransaction(transactionManager?: EntityManager): this
}
@@ -1,4 +1,3 @@
export * from "./cache"
export * from "./event-bus"
export * from "./stock-location"
export * from "./inventory"
export * from "./stock-location"
+16 -9
View File
@@ -1,8 +1,8 @@
import { asValue } from "awilix"
import RealRedis from "ioredis"
import Redis from "ioredis"
import FakeRedis from "ioredis-mock"
import { ConfigModule, MedusaContainer } from "../types/global"
import { Logger } from "../types/global"
import { EOL } from "os"
import { ConfigModule, Logger, MedusaContainer } from "../types/global"
type Options = {
container: MedusaContainer
@@ -10,19 +10,27 @@ type Options = {
logger: Logger
}
// TODO: Will be removed when the strict dependency on Redis in the core is removed
async function redisLoader({
container,
configModule,
logger,
}: Options): Promise<void> {
if (configModule.projectConfig.redis_url) {
// Economical way of dealing with redis clients
const client = new RealRedis(configModule.projectConfig.redis_url)
const subscriber = new RealRedis(configModule.projectConfig.redis_url)
const redisClient = new Redis(configModule.projectConfig.redis_url, {
// Lazy connect to properly handle connection errors
lazyConnect: true,
})
try {
await redisClient.connect()
logger?.info(`Connection to Redis established`)
} catch (err) {
logger?.error(`An error occurred while connecting to Redis:${EOL} ${err}`)
}
container.register({
redisClient: asValue(client),
redisSubscriber: asValue(subscriber),
redisClient: asValue(redisClient),
})
} else {
if (process.env.NODE_ENV === "production") {
@@ -38,7 +46,6 @@ async function redisLoader({
container.register({
redisClient: asValue(client),
redisSubscriber: asValue(client),
})
}
}
+2 -3
View File
@@ -1,7 +1,6 @@
import { MedusaContainer } from "../types/global"
import { Logger } from "../types/global"
import { EventBusService } from "../services"
import { AbstractSearchService } from "../interfaces"
import { EventBusService } from "../services"
import { Logger, MedusaContainer } from "../types/global"
export const SEARCH_INDEX_EVENT = "SEARCH_INDEX_EVENT"
+1 -1
View File
@@ -1,8 +1,8 @@
import { asFunction } from "awilix"
import glob from "glob"
import { isDefined } from "medusa-core-utils"
import path from "path"
import { ConfigModule, MedusaContainer } from "../types/global"
import { isDefined } from "medusa-core-utils"
import formatRegistrationName from "../utils/format-registration-name"
type Options = {
+3 -3
View File
@@ -6,10 +6,12 @@ import {
JoinColumn,
ManyToOne,
OneToMany,
OneToOne,
OneToOne
} from "typeorm"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
import { generateEntityId } from "../utils/generate-entity-id"
import { Address } from "./address"
import { Cart } from "./cart"
import { Fulfillment } from "./fulfillment"
@@ -18,8 +20,6 @@ import { Order } from "./order"
import { Payment } from "./payment"
import { Return } from "./return"
import { ShippingMethod } from "./shipping-method"
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
import { generateEntityId } from "../utils/generate-entity-id"
export enum SwapFulfillmentStatus {
NOT_FULFILLED = "not_fulfilled",
@@ -1,7 +1,6 @@
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
import { dataSource } from "../loaders/database"
import { StagedJob } from "../models"
import { rowSqlResultsToEntityTransformer } from "../utils/row-sql-results-to-entity-transformer"
export const StagedJobRepository = dataSource.getRepository(StagedJob).extend({
async insertBulk(jobToCreates: QueryDeepPartialEntity<StagedJob>[]) {
@@ -13,16 +12,13 @@ export const StagedJobRepository = dataSource.getRepository(StagedJob).extend({
// 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
return rawStagedJobs.generatedMaps.map((d) =>
this.create(d)
) as StagedJob[]
}
const rawStagedJobs = await queryBuilder.returning("*").execute()
return rowSqlResultsToEntityTransformer(
rawStagedJobs.raw,
queryBuilder,
this.queryRunner!
)
return rawStagedJobs.generatedMaps.map((d) => this.create(d))
},
})
export default StagedJobRepository
@@ -0,0 +1,25 @@
const job1 = {
event_name: "test",
data: {
id: "test",
},
}
const StagedJobServiceMock = {
withTransaction: function () {
return this
},
create: jest.fn().mockImplementation((data) => {
return Promise.resolve(data)
}),
list: jest.fn().mockImplementation((config) => {
return Promise.resolve([job1])
}),
}
const mock = jest.fn().mockImplementation(() => {
return StagedJobServiceMock
})
export default mock
@@ -1,168 +1,188 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import BatchJobService from "../batch-job"
import { EventBusService } from "../index"
import { BatchJobStatus } from "../../types/batch-job"
import { BatchJob } from "../../models"
import { BatchJobStatus } from "../../types/batch-job"
import BatchJobService from "../batch-job"
import EventBusService from "../event-bus"
const eventBusServiceMock = {
emit: jest.fn(),
withTransaction: function() {
withTransaction: function () {
return this
},
} as unknown as EventBusService
const batchJobRepositoryMock = MockRepository({
create: jest.fn().mockImplementation((data) => {
return Object.assign(new BatchJob(), data)
})
}),
})
describe('BatchJobService', () => {
describe("BatchJobService", () => {
const batchJobId_1 = IdMap.getId("batchJob_1")
const batchJobService = new BatchJobService({
manager: MockManager,
eventBusService: eventBusServiceMock,
batchJobRepository: batchJobRepositoryMock
batchJobRepository: batchJobRepositoryMock,
} as any)
afterEach(() => {
jest.clearAllMocks()
})
describe('update status', () => {
describe("update status", () => {
describe("confirm", () => {
it('should be able to confirm_processing a batch job to emit the processing event', async () => {
it("should be able to confirm_processing a batch job to emit the processing event", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
dry_run: true,
status: BatchJobStatus.PRE_PROCESSED
status: BatchJobStatus.PRE_PROCESSED,
})
const updatedBatchJob = await batchJobService.confirm(batchJob)
expect(updatedBatchJob.processing_at).not.toBeTruthy()
expect(eventBusServiceMock.emit)
.toHaveBeenCalledWith(BatchJobService.Events.CONFIRMED, { id: batchJobId_1 })
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
BatchJobService.Events.CONFIRMED,
{ id: batchJobId_1 }
)
})
it('should not be able to confirm a batch job with the wrong status', async () => {
it("should not be able to confirm a batch job with the wrong status", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
dry_run: true,
status: BatchJobStatus.CREATED
status: BatchJobStatus.CREATED,
})
const err = await batchJobService.confirm(batchJob)
.catch(e => e)
const err = await batchJobService.confirm(batchJob).catch((e) => e)
expect(err).toBeTruthy()
expect(err.message).toBe("Cannot confirm processing for a batch job that is not pre processed")
expect(err.message).toBe(
"Cannot confirm processing for a batch job that is not pre processed"
)
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
})
})
describe("complete", () => {
it('should be able to complete a batch job', async () => {
it("should be able to complete a batch job", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
dry_run: true,
status: BatchJobStatus.PROCESSING
status: BatchJobStatus.PROCESSING,
})
const updatedBatchJob = await batchJobService.complete(batchJob)
expect(updatedBatchJob.completed_at).toBeTruthy()
expect(eventBusServiceMock.emit)
.toHaveBeenCalledWith(BatchJobService.Events.COMPLETED, { id: batchJobId_1 })
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
BatchJobService.Events.COMPLETED,
{ id: batchJobId_1 }
)
const batchJob2 = batchJobRepositoryMock.create({
id: batchJobId_1,
dry_run: false,
status: BatchJobStatus.PROCESSING
status: BatchJobStatus.PROCESSING,
})
const updatedBatchJob2 = await batchJobService.complete(batchJob2)
expect(updatedBatchJob2.completed_at).toBeTruthy()
expect(eventBusServiceMock.emit)
.toHaveBeenCalledWith(BatchJobService.Events.COMPLETED, { id: batchJobId_1 })
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
BatchJobService.Events.COMPLETED,
{ id: batchJobId_1 }
)
})
it('should not be able to complete a batch job with the wrong status', async () => {
it("should not be able to complete a batch job with the wrong status", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
dry_run: true,
status: BatchJobStatus.CREATED
status: BatchJobStatus.CREATED,
})
const err = await batchJobService.complete(batchJob)
.catch(e => e)
const err = await batchJobService.complete(batchJob).catch((e) => e)
expect(err).toBeTruthy()
expect(err.message).toBe( `Cannot complete a batch job with status "${batchJob.status}". The batch job must be processing`)
expect(err.message).toBe(
`Cannot complete a batch job with status "${batchJob.status}". The batch job must be processing`
)
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
const batchJob2 = batchJobRepositoryMock.create({
id: batchJobId_1,
dry_run: false,
status: BatchJobStatus.PRE_PROCESSED
status: BatchJobStatus.PRE_PROCESSED,
})
const err2 = await batchJobService.complete(batchJob2)
.catch(e => e)
const err2 = await batchJobService.complete(batchJob2).catch((e) => e)
expect(err2).toBeTruthy()
expect(err2.message).toBe( `Cannot complete a batch job with status "${batchJob2.status}". The batch job must be processing`)
expect(err2.message).toBe(
`Cannot complete a batch job with status "${batchJob2.status}". The batch job must be processing`
)
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
})
})
describe("pre processed", () => {
it('should be able to mark as pre processed a batch job in dry_run', async () => {
it("should be able to mark as pre processed a batch job in dry_run", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
dry_run: true,
status: BatchJobStatus.CREATED
status: BatchJobStatus.CREATED,
})
const updatedBatchJob = await batchJobService.setPreProcessingDone(batchJob)
const updatedBatchJob = await batchJobService.setPreProcessingDone(
batchJob
)
expect(updatedBatchJob.pre_processed_at).toBeTruthy()
expect(eventBusServiceMock.emit)
.toHaveBeenCalledWith(BatchJobService.Events.PRE_PROCESSED, { id: batchJobId_1 })
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
BatchJobService.Events.PRE_PROCESSED,
{ id: batchJobId_1 }
)
})
it('should be able to mark as completed a batch job that has been pre processed but not in dry_run', async () => {
it("should be able to mark as completed a batch job that has been pre processed but not in dry_run", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
dry_run: false,
status: BatchJobStatus.CREATED
status: BatchJobStatus.CREATED,
})
const updatedBatchJob = await batchJobService.setPreProcessingDone(batchJob)
const updatedBatchJob = await batchJobService.setPreProcessingDone(
batchJob
)
expect(updatedBatchJob.pre_processed_at).toBeTruthy()
expect(updatedBatchJob.confirmed_at).toBeTruthy()
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(2)
expect(eventBusServiceMock.emit)
.toHaveBeenCalledWith(BatchJobService.Events.PRE_PROCESSED, { id: batchJobId_1 })
expect(eventBusServiceMock.emit)
.toHaveBeenLastCalledWith(BatchJobService.Events.CONFIRMED, { id: batchJobId_1 })
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
BatchJobService.Events.PRE_PROCESSED,
{ id: batchJobId_1 }
)
expect(eventBusServiceMock.emit).toHaveBeenLastCalledWith(
BatchJobService.Events.CONFIRMED,
{ id: batchJobId_1 }
)
})
})
describe("cancel", () => {
it('should be able to cancel a batch job', async () => {
it("should be able to cancel a batch job", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
status: BatchJobStatus.CREATED
status: BatchJobStatus.CREATED,
})
const updatedBatchJob = await batchJobService.cancel(batchJob)
expect(updatedBatchJob.canceled_at).toBeTruthy()
expect(eventBusServiceMock.emit)
.toHaveBeenCalledWith(BatchJobService.Events.CANCELED, { id: batchJobId_1 })
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
BatchJobService.Events.CANCELED,
{ id: batchJobId_1 }
)
})
it('should not be able to cancel a batch job with the wrong status', async () => {
it("should not be able to cancel a batch job with the wrong status", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
status: BatchJobStatus.COMPLETED
status: BatchJobStatus.COMPLETED,
})
const err = await batchJobService.cancel(batchJob)
.catch(e => e)
const err = await batchJobService.cancel(batchJob).catch((e) => e)
expect(err).toBeTruthy()
expect(err.message).toBe("Cannot cancel completed batch job")
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
@@ -170,30 +190,35 @@ describe('BatchJobService', () => {
})
describe("processing", () => {
it('should be able to mark as processing a batch job', async () => {
it("should be able to mark as processing a batch job", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
status: BatchJobStatus.CONFIRMED
status: BatchJobStatus.CONFIRMED,
})
const updatedBatchJob = await batchJobService.setProcessing(batchJob)
expect(updatedBatchJob.processing_at).toBeTruthy()
expect(eventBusServiceMock.emit)
.toHaveBeenCalledWith(BatchJobService.Events.PROCESSING, { id: batchJobId_1 })
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
BatchJobService.Events.PROCESSING,
{ id: batchJobId_1 }
)
})
it('should not be able to mark as processing a batch job with the wrong status', async () => {
it("should not be able to mark as processing a batch job with the wrong status", async () => {
const batchJob = batchJobRepositoryMock.create({
id: batchJobId_1,
status: BatchJobStatus.COMPLETED
status: BatchJobStatus.COMPLETED,
})
const err = await batchJobService.setProcessing(batchJob)
.catch(e => e)
const err = await batchJobService
.setProcessing(batchJob)
.catch((e) => e)
expect(err).toBeTruthy()
expect(err.message).toBe("Cannot mark a batch job as processing if the status is different that confirmed")
expect(err.message).toBe(
"Cannot mark a batch job as processing if the status is different that confirmed"
)
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
})
})
})
})
})
@@ -1,36 +1,35 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { EventBusService } from "../index"
import { Currency } from "../../models"
import CurrencyService from "../currency"
import { FlagRouter } from "../../utils/flag-router"
import TaxInclusivePricingFeatureFlag from "../../loaders/feature-flags/tax-inclusive-pricing"
import { Currency } from "../../models"
import { FlagRouter } from "../../utils/flag-router"
import CurrencyService from "../currency"
import EventBusService from "../event-bus"
const currencyCode = IdMap.getId("currency-1")
const eventBusServiceMock = {
emit: jest.fn(),
withTransaction: function() {
withTransaction: function () {
return this
},
} as unknown as EventBusService
const currencyRepositoryMock = MockRepository({
findOne: jest.fn().mockImplementation(() => {
return {
code: currencyCode
code: currencyCode,
}
}),
save: jest.fn().mockImplementation((data) => {
return Object.assign(new Currency(), data)
})
}),
})
describe('CurrencyService', () => {
describe("CurrencyService", () => {
const currencyService = new CurrencyService({
manager: MockManager,
currencyRepository: currencyRepositoryMock,
eventBusService: eventBusServiceMock,
featureFlagRouter: new FlagRouter({
[TaxInclusivePricingFeatureFlag.key]: true
[TaxInclusivePricingFeatureFlag.key]: true,
}),
})
@@ -1,536 +0,0 @@
import Bull from "bull"
import { MockManager, MockRepository } from "medusa-test-utils"
import config from "../../loaders/config"
import EventBusService from "../event-bus"
jest.genMockFromModule("bull")
jest.mock("bull")
jest.mock("../../loaders/config")
config.redisURI = "testhost"
const loggerMock = {
info: jest.fn().mockReturnValue(console.log),
warn: jest.fn().mockReturnValue(console.log),
error: jest.fn().mockReturnValue(console.log),
}
describe("EventBusService", () => {
describe("constructor", () => {
let eventBus
beforeAll(() => {
jest.resetAllMocks()
const stagedJobRepository = MockRepository({
find: () => Promise.resolve([]),
})
eventBus = new EventBusService(
{
manager: MockManager,
stagedJobRepository,
logger: loggerMock,
},
{
projectConfig: {
redis_url: "localhost",
},
}
)
})
afterAll(async () => {
await eventBus.stopEnqueuer()
})
it("creates bull queue", () => {
expect(Bull).toHaveBeenCalledTimes(1)
expect(Bull).toHaveBeenCalledWith("EventBusService:queue", {
createClient: expect.any(Function),
})
})
})
describe("subscribe", () => {
let eventBus
beforeEach(() => {
jest.resetAllMocks()
eventBus = new EventBusService(
{
manager: MockManager,
logger: loggerMock,
},
{
projectConfig: {
redis_url: "localhost",
},
}
)
})
afterAll(async () => {
await eventBus.stopEnqueuer()
})
it("throws when subscriber already exists", async () => {
expect.assertions(1)
eventBus.subscribe("eventName", () => "test", {
subscriberId: "my-subscriber",
})
try {
eventBus.subscribe("eventName", () => "new", {
subscriberId: "my-subscriber",
})
} catch (error) {
expect(error.message).toBe(
"Subscriber with id my-subscriber already exists"
)
}
})
it("successfully adds subscriber", () => {
eventBus.subscribe("eventName", () => "test", {
subscriberId: "my-subscriber",
})
expect(eventBus.eventToSubscribersMap_.get("eventName").length).toEqual(1)
})
describe("fails when adding non-function subscriber", () => {
let eventBus
beforeAll(() => {
jest.resetAllMocks()
const stagedJobRepository = MockRepository({
find: () => Promise.resolve([]),
})
eventBus = new EventBusService({
manager: MockManager,
stagedJobRepository,
logger: loggerMock,
})
})
afterAll(async () => {
await eventBus.stopEnqueuer()
})
it("rejects subscriber with error", () => {
try {
eventBus.subscribe("eventName", 1234)
} catch (err) {
expect(err.message).toEqual("Subscriber must be a function")
}
})
})
})
describe("emit", () => {
const eventName = "eventName"
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", () => {
let eventBus
let stagedJobRepository
beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
})
eventBus = new EventBusService(
{
logger: loggerMock,
manager: mockManager,
stagedJobRepository,
},
{
projectConfig: {
redis_url: "localhost",
},
}
)
eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
})
afterEach(async () => {
await eventBus.stopEnqueuer()
jest.clearAllMocks()
})
it("calls queue.addBulk", async () => {
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 jobs in bulk to queue", () => {
let eventBus
let stagedJobRepository
beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
})
eventBus = new EventBusService(
{
logger: loggerMock,
manager: mockManager,
stagedJobRepository,
},
{
projectConfig: {
redis_url: "localhost",
},
}
)
eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
})
afterEach(async () => {
jest.clearAllMocks()
await eventBus.stopEnqueuer()
})
it("calls queue.addBulk", async () => {
await eventBus.emit([
{ eventName, data: bulkData[0] },
{ eventName, data: bulkData[1] },
])
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", () => {
let eventBus
let stagedJobRepository
beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
})
eventBus = new EventBusService(
{
logger: loggerMock,
manager: mockManager,
stagedJobRepository,
},
{
projectConfig: {
event_options: { removeOnComplete: 10 },
redis_url: "localhost",
},
}
)
eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
eventBus.emit(eventName, data)
})
afterEach(async () => {
jest.clearAllMocks()
await eventBus.stopEnqueuer()
})
it("calls queue.addBulk", () => {
expect(eventBus.queue_.addBulk).toHaveBeenCalled()
expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
{
data: {
data,
eventName,
},
opts: { removeOnComplete: 10, attempts: 1 },
},
])
})
})
describe("successfully adds job to queue with default options", () => {
let eventBus
let stagedJobRepository
beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
})
eventBus = new EventBusService(
{
logger: loggerMock,
manager: mockManager,
stagedJobRepository,
},
{
projectConfig: {
redis_url: "localhost",
},
}
)
eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
eventBus.emit(eventName, data)
})
afterEach(async () => {
jest.clearAllMocks()
await eventBus.stopEnqueuer()
})
it("calls queue.addBulk", () => {
expect(eventBus.queue_.addBulk).toHaveBeenCalled()
expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
{
data: {
data,
eventName,
},
opts: { removeOnComplete: true, attempts: 1 },
},
])
})
})
describe("successfully adds job to queue with local options and global options merged", () => {
let eventBus
let stagedJobRepository
beforeEach(() => {
stagedJobRepository = MockRepository({
insertBulk: async (data) => data,
create: (data) => data,
})
eventBus = new EventBusService(
{
logger: loggerMock,
manager: MockManager,
stagedJobRepository,
},
{
projectConfig: {
event_options: { removeOnComplete: 10 },
redis_url: "localhost",
},
}
)
eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
eventBus.emit(eventName, data, {
attempts: 10,
delay: 1000,
backoff: { type: "exponential" },
})
})
afterEach(async () => {
jest.clearAllMocks()
await eventBus.stopEnqueuer()
})
it("calls queue.add", () => {
expect(eventBus.queue_.addBulk).toHaveBeenCalled()
expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
{
data: {
data,
eventName,
},
opts: {
removeOnComplete: 10, // global option
attempts: 10, // local option
delay: 1000, // local option
backoff: { type: "exponential" }, // local option
},
},
])
})
})
})
describe("worker", () => {
let eventBus
let result
describe("successfully runs the worker", () => {
beforeAll(async () => {
jest.resetAllMocks()
const stagedJobRepository = MockRepository({
find: () => Promise.resolve([]),
})
eventBus = new EventBusService({
manager: MockManager,
stagedJobRepository,
logger: loggerMock,
})
eventBus.subscribe("eventName", () => Promise.resolve("hi"))
result = await eventBus.worker_({
data: { eventName: "eventName", data: {} },
})
})
afterAll(async () => {
await eventBus.stopEnqueuer()
})
it("calls logger", () => {
expect(loggerMock.info).toHaveBeenCalled()
expect(loggerMock.info).toHaveBeenCalledWith(
"Processing eventName which has 1 subscribers"
)
})
it("returns array with hi", async () => {
expect(result).toEqual(["hi"])
})
})
describe("continue if errors occur", () => {
let eventBus
beforeAll(async () => {
jest.resetAllMocks()
eventBus = new EventBusService({
manager: MockManager,
logger: loggerMock,
})
eventBus.subscribe("eventName", () => Promise.resolve("hi"))
eventBus.subscribe("eventName", () => Promise.resolve("hi2"))
eventBus.subscribe("eventName", () => Promise.resolve("hi3"))
eventBus.subscribe("eventName", () => Promise.reject("fail1"))
eventBus.subscribe("eventName", () => Promise.reject("fail2"))
eventBus.subscribe("eventName", () => Promise.reject("fail3"))
result = await eventBus.worker_({
data: { eventName: "eventName", data: {} },
update: (data) => data,
opts: { attempts: 1 },
})
})
afterAll(async () => {
await eventBus.stopEnqueuer()
})
it("calls logger warn on rejections", () => {
expect(loggerMock.warn).toHaveBeenCalledTimes(4)
expect(loggerMock.warn).toHaveBeenCalledWith(
"An error occurred while processing eventName: fail1"
)
expect(loggerMock.warn).toHaveBeenCalledWith(
"An error occurred while processing eventName: fail2"
)
expect(loggerMock.warn).toHaveBeenCalledWith(
"An error occurred while processing eventName: fail3"
)
})
it("calls logger warn from retry not kicking in", () => {
expect(loggerMock.warn).toHaveBeenCalledWith(
"One or more subscribers of eventName failed. Retrying is not configured. Use 'attempts' option when emitting events."
)
})
})
})
})
@@ -1,12 +1,9 @@
import Bull from "bull"
import config from "../../loaders/config"
import { Queue } from "bullmq"
import JobSchedulerService from "../job-scheduler"
jest.genMockFromModule("bull")
jest.mock("bull")
jest.mock("../../loaders/config")
config.redisURI = "testhost"
jest.genMockFromModule("bullmq")
jest.mock("bullmq")
jest.mock("ioredis")
const loggerMock = {
info: jest.fn().mockReturnValue(console.log),
@@ -15,90 +12,112 @@ const loggerMock = {
}
describe("JobSchedulerService", () => {
describe("constructor", () => {
let jobScheduler
beforeAll(() => {
jest.resetAllMocks()
let scheduler
jobScheduler = new JobSchedulerService({
logger: loggerMock,
})
describe("constructor", () => {
beforeAll(() => {
jest.clearAllMocks()
scheduler = new JobSchedulerService(
{
logger: loggerMock,
},
{
projectConfig: {
redis_url: "testhost",
},
}
)
})
it("creates bull queue", () => {
expect(Bull).toHaveBeenCalledTimes(1)
expect(Bull).toHaveBeenCalledWith("scheduled-jobs:queue", {
createClient: expect.any(Function),
expect(Queue).toHaveBeenCalledTimes(1)
expect(Queue).toHaveBeenCalledWith("scheduled-jobs:queue", {
connection: expect.any(Object),
prefix: "JobSchedulerService",
})
})
})
describe("create", () => {
let jobScheduler
describe("successfully creates scheduled job and add handler", () => {
beforeAll(() => {
jest.resetAllMocks()
jobScheduler = new JobSchedulerService({
beforeAll(async () => {
jest.resetAllMocks()
jobScheduler = new JobSchedulerService(
{
logger: loggerMock,
})
jobScheduler.create(
"eventName",
{ data: "test" },
"* * * * *",
() => "test"
)
})
it("added the handler to the job queue", () => {
expect(jobScheduler.handlers_.get("eventName").length).toEqual(1)
expect(jobScheduler.queue_.add).toHaveBeenCalledWith(
{
eventName: "eventName",
data: { data: "test" },
},
{
projectConfig: {
redis_url: "testhost",
},
{
repeat: { cron: "* * * * *" },
}
)
}
)
await jobScheduler.create(
"eventName",
{ data: "test" },
"* * * * *",
() => "test"
)
})
it("added the handler to the job queue", () => {
expect(jobScheduler.handlers_.get("eventName").length).toEqual(1)
expect(jobScheduler.queue_.add).toHaveBeenCalledWith(
"eventName",
{
eventName: "eventName",
data: { data: "test" },
},
{
repeat: { pattern: "* * * * *" },
}
)
})
})
describe("scheduledJobWorker", () => {
let jobScheduler
let result
beforeAll(async () => {
jest.resetAllMocks()
jobScheduler = new JobSchedulerService(
{
logger: loggerMock,
},
{
projectConfig: {
redis_url: "testhost",
},
}
)
await jobScheduler.create(
"eventName",
{ data: "test" },
"* * * * *",
() => Promise.resolve("hi")
)
result = await jobScheduler.scheduledJobsWorker({
data: { eventName: "eventName", data: {} },
})
})
describe("scheduledJobWorker", () => {
let jobScheduler
let result
describe("successfully runs the worker", () => {
beforeAll(async () => {
jest.resetAllMocks()
it("calls logger", () => {
expect(loggerMock.info).toHaveBeenCalled()
expect(loggerMock.info).toHaveBeenCalledWith(
"Processing scheduled job: eventName"
)
})
jobScheduler = new JobSchedulerService(
{
logger: loggerMock,
},
{}
)
jobScheduler.create("eventName", { data: "test" }, "* * * * *", () =>
Promise.resolve("hi")
)
result = await jobScheduler.scheduledJobsWorker({
data: { eventName: "eventName", data: {} },
})
})
it("calls logger", () => {
expect(loggerMock.info).toHaveBeenCalled()
expect(loggerMock.info).toHaveBeenCalledWith(
"Processing scheduled job: eventName"
)
})
it("returns array with hi", async () => {
expect(result).toEqual(["hi"])
})
})
it("returns array with hi", async () => {
expect(result).toEqual(["hi"])
})
})
})
@@ -1,7 +1,12 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { EventBusService, LineItemService, OrderEditItemChangeService, TaxProviderService, } from "../index"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import { In } from "typeorm"
import EventBusService from "../event-bus"
import {
LineItemService,
OrderEditItemChangeService,
TaxProviderService
} from "../index"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import { LineItemServiceMock } from "../__mocks__/line-item"
const taxProviderServiceMock = {
@@ -1,7 +1,7 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { OrderEditItemChangeType, OrderEditStatus } from "../../models"
import EventBusService from "../event-bus"
import {
EventBusService,
LineItemService,
NewTotalsService,
OrderEditItemChangeService,
@@ -1,10 +1,22 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { CustomerService, EventBusService, PaymentCollectionService, PaymentProviderService, } from "../index"
import { PaymentCollection, PaymentCollectionStatus, PaymentCollectionType, } from "../../models"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import { DefaultProviderMock, PaymentProviderServiceMock, } from "../__mocks__/payment-provider"
import { CustomerServiceMock } from "../__mocks__/customer"
import {
PaymentCollection,
PaymentCollectionStatus,
PaymentCollectionType
} from "../../models"
import { PaymentCollectionsSessionsBatchInput } from "../../types/payment-collection"
import EventBusService from "../event-bus"
import {
CustomerService,
PaymentCollectionService,
PaymentProviderService
} from "../index"
import { CustomerServiceMock } from "../__mocks__/customer"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import {
DefaultProviderMock,
PaymentProviderServiceMock
} from "../__mocks__/payment-provider"
describe("PaymentCollectionService", () => {
afterEach(() => {
@@ -414,9 +426,7 @@ describe("PaymentCollectionService", () => {
"lebron"
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
})
@@ -430,12 +440,8 @@ describe("PaymentCollectionService", () => {
"lebron"
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
0
)
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(0)
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(1)
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
})
@@ -453,15 +459,9 @@ describe("PaymentCollectionService", () => {
IdMap.getId("lebron")
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(
0
)
expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(0)
expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes(1)
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
})
@@ -508,9 +508,7 @@ describe("PaymentCollectionService", () => {
"customer1"
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
0
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(0)
expect(ret).rejects.toThrow(
`The sum of sessions is not equal to 100 on Payment Collection`
)
@@ -531,9 +529,7 @@ describe("PaymentCollectionService", () => {
"customer1"
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
0
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(0)
expect(multiRet).rejects.toThrow(
`The sum of sessions is not equal to 100 on Payment Collection`
)
@@ -580,12 +576,8 @@ describe("PaymentCollectionService", () => {
"lebron"
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(1)
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
})
@@ -603,15 +595,9 @@ describe("PaymentCollectionService", () => {
IdMap.getId("lebron")
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(
0
)
expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(0)
expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes(1)
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
})
@@ -623,13 +609,9 @@ describe("PaymentCollectionService", () => {
"customer1"
)
expect(
PaymentProviderServiceMock.refreshSession
).toHaveBeenCalledTimes(1)
expect(PaymentProviderServiceMock.refreshSession).toHaveBeenCalledTimes(1)
expect(DefaultProviderMock.deletePayment).toHaveBeenCalledTimes(1)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
})
it("should throw to refresh a payment session that doesn't exist", async () => {
@@ -689,9 +671,7 @@ describe("PaymentCollectionService", () => {
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
2
)
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(
2
)
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(2)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
})
@@ -704,9 +684,7 @@ describe("PaymentCollectionService", () => {
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
})
@@ -719,9 +697,7 @@ describe("PaymentCollectionService", () => {
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
0
)
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(
0
)
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(0)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
})
})
@@ -1,13 +1,10 @@
import { IdMap, MockManager as manager } from "medusa-test-utils"
import ProductCategoryService from "../product-category"
import { EventBusService } from "../"
import { EventBusService, ProductCategoryService } from "../"
import {
invalidProdCategoryId,
productCategoryRepositoryMock as productCategoryRepository,
validProdCategoryId,
validProdCategoryIdWithChildren,
validProdCategoryWithSiblings,
validProdCategoryRankChange
validProdCategoryIdWithChildren, validProdCategoryRankChange, validProdCategoryWithSiblings
} from "../../repositories/__mocks__/product-category"
import { tempReorderRank } from "../../types/product-category"
import { EventBusServiceMock as eventBusService } from "../__mocks__/event-bus"
@@ -74,7 +71,9 @@ describe("ProductCategoryService", () => {
const [result, count] = await productCategoryService
.listAndCount({ q: IdMap.getId(invalidProdCategoryId) })
expect(productCategoryRepository.getFreeTextSearchResultsAndCount).toHaveBeenCalledTimes(1)
expect(
productCategoryRepository.getFreeTextSearchResultsAndCount
).toHaveBeenCalledTimes(1)
expect(result).toEqual([])
expect(count).toEqual(0)
})
@@ -115,8 +114,9 @@ describe("ProductCategoryService", () => {
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
expect(eventBusService.emit).toHaveBeenCalledWith(
"product-category.created", {
"id": IdMap.getId(validProdCategoryId)
"product-category.created",
{
id: IdMap.getId(validProdCategoryId),
}
)
})
@@ -129,7 +129,9 @@ describe("ProductCategoryService", () => {
)
expect(productCategoryRepository.delete).toBeCalledTimes(1)
expect(productCategoryRepository.delete).toBeCalledWith(IdMap.getId(validProdCategoryId))
expect(productCategoryRepository.delete).toBeCalledWith(
IdMap.getId(validProdCategoryId)
)
})
it("returns without failure on not-found product category id", async () => {
@@ -156,8 +158,9 @@ describe("ProductCategoryService", () => {
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
expect(eventBusService.emit).toHaveBeenCalledWith(
"product-category.deleted", {
"id": IdMap.getId(validProdCategoryId)
"product-category.deleted",
{
id: IdMap.getId(validProdCategoryId),
}
)
})
@@ -182,11 +185,9 @@ describe("ProductCategoryService", () => {
describe("update", () => {
it("successfully updates a product category", async () => {
await productCategoryService.update(
IdMap.getId(validProdCategoryId), {
name: "bathrobes",
}
)
await productCategoryService.update(IdMap.getId(validProdCategoryId), {
name: "bathrobes",
})
expect(productCategoryRepository.save).toHaveBeenCalledTimes(1)
expect(productCategoryRepository.save).toHaveBeenCalledWith(
@@ -221,28 +222,32 @@ describe("ProductCategoryService", () => {
})
it("fails on not-found Id product category", async () => {
const error = await productCategoryService.update(
IdMap.getId(invalidProdCategoryId), {
const error = await productCategoryService
.update(IdMap.getId(invalidProdCategoryId), {
name: "bathrobes",
}
).catch(e => e)
})
.catch((e) => e)
expect(error.message).toBe(
`ProductCategory with id: ${IdMap.getId(invalidProdCategoryId)} was not found`
`ProductCategory with id: ${IdMap.getId(
invalidProdCategoryId
)} was not found`
)
})
it("emits a message on successful update", async () => {
const result = await productCategoryService.update(
IdMap.getId(validProdCategoryId), {
IdMap.getId(validProdCategoryId),
{
name: "bathrobes",
}
)
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
expect(eventBusService.emit).toHaveBeenCalledWith(
"product-category.updated", {
"id": IdMap.getId(validProdCategoryId)
"product-category.updated",
{
id: IdMap.getId(validProdCategoryId),
}
)
})
@@ -1,8 +1,8 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import EventBusService from "../event-bus"
import { EventBusService } from "../index"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import PublishableApiKeyService from "../publishable-api-key"
import { EventBusServiceMock } from "../__mocks__/event-bus"
const pubKeyToRetrieve = {
id: IdMap.getId("pub-key-to-retrieve"),
@@ -1,8 +1,8 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { CreateRegionInput } from "../../types/region"
import { FlagRouter } from "../../utils/flag-router"
import EventBusService from "../event-bus"
import {
EventBusService,
FulfillmentProviderService,
PaymentProviderService,
StoreService,
@@ -1,9 +1,7 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { EventBusService, StoreService } from "../index"
import SalesChannelService from "../sales-channel"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import { EventBusService, StoreService } from "../index"
import { FindManyOptions, FindOneOptions } from "typeorm"
import { SalesChannel } from "../../models"
import { store, StoreServiceMock } from "../__mocks__/store"
describe("SalesChannelService", () => {
+12 -12
View File
@@ -1,14 +1,10 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import SwapService from "../swap"
import {
ProductVariantInventoryServiceMock
} from "../__mocks__/product-variant-inventory"
import {
LineItemAdjustmentServiceMock
} from "../__mocks__/line-item-adjustment"
import { Order, Swap } from "../../models"
import { SwapRepository } from "../../repositories/swap"
import CartService from "../cart"
import EventBusService from "../event-bus"
import {
CustomShippingOptionService,
EventBusService,
FulfillmentService,
LineItemService,
OrderService,
@@ -16,12 +12,16 @@ import {
ProductVariantInventoryService,
ReturnService,
ShippingOptionService,
TotalsService,
TotalsService
} from "../index"
import CartService from "../cart"
import { Order, Swap } from "../../models"
import { SwapRepository } from "../../repositories/swap"
import LineItemAdjustmentService from "../line-item-adjustment"
import SwapService from "../swap"
import {
LineItemAdjustmentServiceMock
} from "../__mocks__/line-item-adjustment"
import {
ProductVariantInventoryServiceMock
} from "../__mocks__/product-variant-inventory"
/* ******************** DEFAULT REPOSITORY MOCKS ******************** */
+5 -4
View File
@@ -1,4 +1,7 @@
import { Request } from "express"
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import { TransactionBaseService } from "../interfaces"
import { BatchJob } from "../models"
import { BatchJobRepository } from "../repositories/batch-job"
import {
@@ -10,11 +13,9 @@ import {
FilterableBatchJobProps,
} from "../types/batch-job"
import { FindConfig } from "../types/common"
import { TransactionBaseService } from "../interfaces"
import { buildQuery } from "../utils"
import { isDefined, MedusaError } from "medusa-core-utils"
import { EventBusService, StrategyResolverService } from "./index"
import { Request } from "express"
import EventBusService from "./event-bus"
import { StrategyResolverService } from "./index"
type InjectedDependencies = {
manager: EntityManager
@@ -1,6 +1,6 @@
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import { EventBusService } from "."
import { TransactionBaseService } from "../interfaces"
import {
DiscountCondition,
DiscountConditionCustomerGroup,
@@ -13,8 +13,8 @@ import {
import { DiscountConditionRepository } from "../repositories/discount-condition"
import { FindConfig } from "../types/common"
import { DiscountConditionInput } from "../types/discount"
import { TransactionBaseService } from "../interfaces"
import { buildQuery, PostgresError } from "../utils"
import EventBusService from "./event-bus"
type InjectedDependencies = {
manager: EntityManager
+2 -2
View File
@@ -9,7 +9,6 @@ import {
In,
} from "typeorm"
import {
EventBusService,
NewTotalsService,
ProductService,
RegionService,
@@ -36,12 +35,13 @@ import {
UpdateDiscountInput,
UpdateDiscountRuleInput,
} from "../types/discount"
import { CalculationContextData } from "../types/totals"
import { buildQuery, setMetadata } from "../utils"
import { isFuture, isPast } from "../utils/date-helpers"
import { FlagRouter } from "../utils/flag-router"
import CustomerService from "./customer"
import DiscountConditionService from "./discount-condition"
import { CalculationContextData } from "../types/totals"
import EventBusService from "./event-bus"
/**
* Provides layer to manipulate discounts.
+84 -350
View File
@@ -1,150 +1,68 @@
import Bull, { JobOptions } from "bull"
import Redis from "ioredis"
import { DeepPartial, EntityManager, In } from "typeorm"
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
import { ulid } from "ulid"
import { EventBusTypes } from "@medusajs/types"
import { EventBusUtils } from "@medusajs/utils"
import { EntityManager } from "typeorm"
import { TransactionBaseService } from "../interfaces"
import { StagedJob } from "../models"
import { StagedJobRepository } from "../repositories/staged-job"
import { ConfigModule, Logger } from "../types/global"
import { ConfigModule } from "../types/global"
import { isString } from "../utils"
import { sleep } from "../utils/sleep"
import JobSchedulerService, { CreateJobOptions } from "./job-scheduler"
import StagedJobService from "./staged-job"
type InjectedDependencies = {
manager: EntityManager
logger: Logger
stagedJobRepository: typeof StagedJobRepository
jobSchedulerService: JobSchedulerService
redisClient: Redis.Redis
redisSubscriber: Redis.Redis
}
type Subscriber<T = unknown> = (data: T, eventName: string) => Promise<void>
type SubscriberContext = {
subscriberId: string
}
type BullJob<T> = {
update: (data: unknown) => void
attemptsMade: number
opts: EmitOptions
data: {
eventName: string
data: T
completedSubscriberIds: string[] | undefined
}
}
type SubscriberDescriptor = {
id: string
subscriber: Subscriber
}
export type EmitOptions = {
delay?: number
attempts: number
backoff?: {
type: "fixed" | "exponential"
delay: number
}
} & JobOptions
export type EmitData<T = unknown> = {
eventName: string
data: T
opts?: Record<string, unknown> & EmitOptions
stagedJobService: StagedJobService
eventBusModuleService: EventBusUtils.AbstractEventBusModuleService
}
/**
* Can keep track of multiple subscribers to different events and run the
* subscribers when events happen. Events will run asynchronously.
*/
export default class EventBusService {
export default class EventBusService
extends TransactionBaseService
implements EventBusTypes.IEventBusService
{
protected readonly config_: ConfigModule
protected readonly manager_: EntityManager
protected readonly logger_: Logger
protected readonly stagedJobRepository_: typeof StagedJobRepository
protected readonly jobSchedulerService_: JobSchedulerService
protected readonly eventToSubscribersMap_: Map<
string | symbol,
SubscriberDescriptor[]
>
protected readonly redisClient_: Redis.Redis
protected readonly redisSubscriber_: Redis.Redis
protected queue_: Bull
protected readonly stagedJobService_: StagedJobService
// eslint-disable-next-line max-len
protected readonly eventBusModuleService_: EventBusUtils.AbstractEventBusModuleService
protected shouldEnqueuerRun: boolean
protected transactionManager_: EntityManager | undefined
protected enqueue_: Promise<void>
constructor(
{
manager,
logger,
stagedJobRepository,
redisClient,
redisSubscriber,
jobSchedulerService,
}: InjectedDependencies,
config: ConfigModule,
singleton = true
{ stagedJobService, eventBusModuleService }: InjectedDependencies,
config,
isSingleton = true
) {
// eslint-disable-next-line prefer-rest-params
super(arguments[0])
this.config_ = config
this.manager_ = manager
this.logger_ = logger
this.jobSchedulerService_ = jobSchedulerService
this.stagedJobRepository_ = stagedJobRepository
this.eventBusModuleService_ = eventBusModuleService
this.stagedJobService_ = stagedJobService
if (singleton) {
const opts = {
createClient: (type: string): Redis.Redis => {
switch (type) {
case "client":
return redisClient
case "subscriber":
return redisSubscriber
default:
if (config.projectConfig.redis_url) {
return new Redis(config.projectConfig.redis_url)
}
return redisClient
}
},
}
this.eventToSubscribersMap_ = new Map()
this.queue_ = new Bull(`${this.constructor.name}:queue`, opts)
this.redisClient_ = redisClient
this.redisSubscriber_ = redisSubscriber
// Register our worker to handle emit calls
this.queue_.process(this.worker_)
if (process.env.NODE_ENV !== "test") {
this.startEnqueuer()
}
if (process.env.NODE_ENV !== "test" && isSingleton) {
this.startEnqueuer()
}
}
withTransaction(transactionManager): this | EventBusService {
withTransaction(transactionManager?: EntityManager): this {
if (!transactionManager) {
return this
}
const cloned = new EventBusService(
const cloned = new (this.constructor as any)(
{
manager: transactionManager,
stagedJobRepository: this.stagedJobRepository_,
jobSchedulerService: this.jobSchedulerService_,
logger: this.logger_,
redisClient: this.redisClient_,
redisSubscriber: this.redisSubscriber_,
stagedJobService: this.stagedJobService_,
eventBusModuleService: this.eventBusModuleService_,
},
this.config_,
false
)
cloned.manager_ = transactionManager
cloned.transactionManager_ = transactionManager
cloned.queue_ = this.queue_
return cloned
}
@@ -153,70 +71,36 @@ export default class EventBusService {
* Adds a function to a list of event subscribers.
* @param event - the event that the subscriber will listen for.
* @param subscriber - the function to be called when a certain event
* @param context - context to use when attaching subscriber
* happens. Subscribers must return a Promise.
* @param context - subscriber context
* @return this
*/
subscribe(
event: string | symbol,
subscriber: Subscriber,
context?: SubscriberContext
subscriber: EventBusTypes.Subscriber,
context?: EventBusTypes.SubscriberContext
): this {
if (typeof subscriber !== "function") {
throw new Error("Subscriber must be a function")
}
/**
* If context is provided, we use the subscriberId from it
* otherwise we generate a random using a ulid
*/
const subscriberId =
context?.subscriberId ?? `${event.toString()}-${ulid()}`
const newSubscriberDescriptor = { subscriber, id: subscriberId }
const existingSubscribers = this.eventToSubscribersMap_.get(event) ?? []
const subscriberAlreadyExists = existingSubscribers.find(
(sub) => sub.id === subscriberId
)
if (subscriberAlreadyExists) {
throw Error(`Subscriber with id ${subscriberId} already exists`)
}
this.eventToSubscribersMap_.set(event, [
...existingSubscribers,
newSubscriberDescriptor,
])
this.eventBusModuleService_.subscribe(event, subscriber, context)
return this
}
/**
* Adds a function to a list of event subscribers.
* @param event - the event that the subscriber will listen for.
* @param subscriber - the function to be called when a certain event
* happens. Subscribers must return a Promise.
* Removes function from the list of event subscribers.
* @param event - the event of the subcriber.
* @param subscriber - the function to be removed
* @param context - subscriber context
* @return this
*/
unsubscribe(event: string | symbol, subscriber: Subscriber): this {
if (typeof subscriber !== "function") {
throw new Error("Subscriber must be a function")
}
const existingSubscribers = this.eventToSubscribersMap_.get(event)
if (existingSubscribers?.length) {
const subIndex = existingSubscribers?.findIndex(
(sub) => sub.subscriber === subscriber
)
if (subIndex !== -1) {
this.eventToSubscribersMap_.get(event)?.splice(subIndex as number, 1)
}
}
unsubscribe(
event: string | symbol,
subscriber: EventBusTypes.Subscriber,
context: EventBusTypes.SubscriberContext
): this {
this.eventBusModuleService_.unsubscribe(event, subscriber, context)
return this
}
@@ -225,7 +109,7 @@ export default class EventBusService {
* @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>
async emit<T>(data: EventBusTypes.EmitData<T>[]): Promise<StagedJob[] | void>
/**
* Calls all subscribers when an event occurs.
@@ -237,80 +121,54 @@ export default class EventBusService {
async emit<T>(
eventName: string,
data: T,
options?: Record<string, unknown> & EmitOptions
options?: Record<string, unknown>
): Promise<StagedJob | void>
async emit<
T,
TInput extends string | EmitData<T>[] = string,
TResult = TInput extends EmitData<T>[] ? StagedJob[] : StagedJob
TInput extends string | EventBusTypes.EmitData<T>[] = string,
TResult = TInput extends EventBusTypes.EmitData<T>[]
? StagedJob[]
: StagedJob
>(
eventNameOrData: TInput,
data?: T,
options: Record<string, unknown> & EmitOptions = {}
options: Record<string, unknown> = {}
): Promise<TResult | void> {
const globalEventOptions = this.config_?.projectConfig?.event_options ?? {}
const manager = this.activeManager_
const isBulkEmit = !isString(eventNameOrData)
const events = isBulkEmit
? eventNameOrData.map((event) => ({
data: { eventName: event.eventName, data: event.data },
opts: event.opts,
eventName: event.eventName,
data: event.data,
options: event.options,
}))
: [
{
data: { eventName: eventNameOrData, data },
opts: options,
eventName: eventNameOrData,
data: data,
options: options,
},
]
// The order of precedence for job options is:
// 1. local options
// 2. global options
// 3. default options
const defaultOptions: EmitOptions = {
attempts: 1, // default
removeOnComplete: true, // default
...globalEventOptions, // global
}
for (const event of events) {
event.opts = {
...defaultOptions,
...(event.opts ?? {}), // local
}
}
/**
* If we are in an ongoing transaction, we store the jobs in the database
* instead of processing them immediately. We only want to process those
* events, if the transaction successfully commits. This is to avoid jobs
* being processed if the transaction fails.
* We store events in the database when in an ongoing transaction.
*
* In case of a failing transaction, kobs stored in the database are removed
* If we are in a long-running transaction, the ACID properties of a
* transaction ensure, that events are kept invisible to the enqueuer
* until the trasaction has commited.
*
* This patterns also gives us at-least-once delivery of events, as events
* are only removed from the database, if they are successfully delivered.
*
* In case of a failing transaction, jobs stored in the database are removed
* as part of the rollback.
*/
if (this.transactionManager_) {
const stagedJobRepository = this.transactionManager_.withRepository(
this.stagedJobRepository_
)
const stagedJobs = await this.stagedJobService_
.withTransaction(manager)
.create(events)
const jobsToCreate = events.map((event) => {
return stagedJobRepository.create({
event_name: event.data.eventName,
data: event.data.data,
options: event.opts,
} as DeepPartial<StagedJob>) as QueryDeepPartialEntity<StagedJob>
})
const stagedJobs = await stagedJobRepository.insertBulk(jobsToCreate)
return (!isBulkEmit ? stagedJobs[0] : stagedJobs) as unknown as TResult
}
if (this.config_?.projectConfig?.redis_url) {
await this.queue_.addBulk(events)
}
return (!isBulkEmit ? stagedJobs[0] : stagedJobs) as unknown as TResult
}
startEnqueuer(): void {
@@ -324,17 +182,14 @@ export default class EventBusService {
}
async enqueuer_(): Promise<void> {
while (this.shouldEnqueuerRun) {
const listConfig = {
relations: [],
skip: 0,
take: 1000,
}
const listConfig = {
relations: [],
skip: 0,
take: 1000,
}
const stagedJobRepo = this.manager_.withRepository(
this.stagedJobRepository_
)
const jobs = await stagedJobRepo.find(listConfig)
while (this.shouldEnqueuerRun) {
const jobs = await this.stagedJobService_.list(listConfig)
if (!jobs.length) {
await sleep(3000)
@@ -343,138 +198,17 @@ export default class EventBusService {
const eventsData = jobs.map((job) => {
return {
data: { eventName: job.event_name, data: job.data },
opts: { jobId: job.id, ...job.options },
eventName: job.event_name,
data: job.data,
options: { jobId: job.id, ...job.options },
}
})
await this.queue_.addBulk(eventsData).then(async () => {
return await stagedJobRepo.delete({ id: In(jobs.map((j) => j.id)) })
await this.eventBusModuleService_.emit(eventsData).then(async () => {
return await this.stagedJobService_.delete(jobs.map((j) => j.id))
})
await sleep(3000)
}
}
/**
* Handles incoming jobs.
* @param job The job object
* @return resolves to the results of the subscriber calls.
*/
worker_ = async <T>(job: BullJob<T>): Promise<unknown> => {
const { eventName, data } = job.data
const eventSubscribers = this.eventToSubscribersMap_.get(eventName) || []
const wildcardSubscribers = this.eventToSubscribersMap_.get("*") || []
const allSubscribers = eventSubscribers.concat(wildcardSubscribers)
// Pull already completed subscribers from the job data
const completedSubscribers = job.data.completedSubscriberIds || []
// Filter out already completed subscribers from the all subscribers
const subscribersInCurrentAttempt = allSubscribers.filter(
(subscriber) =>
subscriber.id && !completedSubscribers.includes(subscriber.id)
)
const isRetry = job.attemptsMade > 0
const currentAttempt = job.attemptsMade + 1
const isFinalAttempt = job?.opts?.attempts === currentAttempt
if (isRetry) {
if (isFinalAttempt) {
this.logger_.info(`Final retry attempt for ${eventName}`)
}
this.logger_.info(
`Retrying ${eventName} which has ${eventSubscribers.length} subscribers (${subscribersInCurrentAttempt.length} of them failed)`
)
} else {
this.logger_.info(
`Processing ${eventName} which has ${eventSubscribers.length} subscribers`
)
}
const completedSubscribersInCurrentAttempt: string[] = []
const subscribersResult = await Promise.all(
subscribersInCurrentAttempt.map(async ({ id, subscriber }) => {
return subscriber(data, eventName)
.then((data) => {
// For every subscriber that completes successfully, add their id to the list of completed subscribers
completedSubscribersInCurrentAttempt.push(id)
return data
})
.catch((err) => {
this.logger_.warn(
`An error occurred while processing ${eventName}: ${err}`
)
return err
})
})
)
// If the number of completed subscribers is different from the number of subcribers to process in current attempt, some of them failed
const didSubscribersFail =
completedSubscribersInCurrentAttempt.length !==
subscribersInCurrentAttempt.length
const isRetriesConfigured = job?.opts?.attempts > 1
// Therefore, if retrying is configured, we try again
const shouldRetry =
didSubscribersFail && isRetriesConfigured && !isFinalAttempt
if (shouldRetry) {
const updatedCompletedSubscribers = [
...completedSubscribers,
...completedSubscribersInCurrentAttempt,
]
job.data.completedSubscriberIds = updatedCompletedSubscribers
job.update(job.data)
const errorMessage = `One or more subscribers of ${eventName} failed. Retrying...`
this.logger_.warn(errorMessage)
return Promise.reject(Error(errorMessage))
}
if (didSubscribersFail && !isFinalAttempt) {
// If retrying is not configured, we log a warning to allow server admins to recover manually
this.logger_.warn(
`One or more subscribers of ${eventName} failed. Retrying is not configured. Use 'attempts' option when emitting events.`
)
}
return Promise.resolve(subscribersResult)
}
/**
* Registers a cron job.
* @deprecated All cron job logic has been refactored to the `JobSchedulerService`. This method will be removed in a future release.
* @param eventName - the name of the event
* @param data - the data to be sent with the event
* @param cron - the cron pattern
* @param handler - the handler to call on each cron job
* @return void
*/
async createCronJob<T>(
eventName: string,
data: T,
cron: string,
handler: Subscriber,
options?: CreateJobOptions
): Promise<void> {
await this.jobSchedulerService_.create(
eventName,
data,
cron,
handler,
options ?? {}
)
}
}
+1 -1
View File
@@ -1,7 +1,6 @@
import { isDefined, MedusaError } from "medusa-core-utils"
import randomize from "randomatic"
import { EntityManager } from "typeorm"
import { EventBusService } from "."
import { TransactionBaseService } from "../interfaces"
import { GiftCard, Region } from "../models"
import { GiftCardRepository } from "../repositories/gift-card"
@@ -13,6 +12,7 @@ import {
UpdateGiftCardInput,
} from "../types/gift-card"
import { buildQuery, setMetadata } from "../utils"
import EventBusService from "./event-bus"
import RegionService from "./region"
type InjectedDependencies = {
+5 -5
View File
@@ -19,33 +19,34 @@ export { default as IdempotencyKeyService } from "./idempotency-key"
export { default as LineItemService } from "./line-item"
export { default as LineItemAdjustmentService } from "./line-item-adjustment"
export { default as MiddlewareService } from "./middleware"
export { default as NewTotalsService } from "./new-totals"
export { default as NoteService } from "./note"
export { default as NotificationService } from "./notification"
export { default as OauthService } from "./oauth"
export { default as OrderService } from "./order"
export { default as OrderEditService } from "./order-edit"
export { default as OrderEditItemChangeService } from "./order-edit-item-change"
export { default as PaymentService } from "./payment"
export { default as PaymentCollectionService } from "./payment-collection"
export { default as PaymentProviderService } from "./payment-provider"
export { default as PaymentService } from "./payment"
export { default as PriceListService } from "./price-list"
export { default as PricingService } from "./pricing"
export { default as ProductService } from "./product"
export { default as ProductCategoryService } from "./product-category"
export { default as ProductCollectionService } from "./product-collection"
export { default as ProductTypeService } from "./product-type"
export { default as ProductVariantInventoryService } from "./product-variant-inventory"
export { default as ProductVariantService } from "./product-variant"
export { default as ProductVariantInventoryService } from "./product-variant-inventory"
export { default as RegionService } from "./region"
export { default as ReturnService } from "./return"
export { default as ReturnReasonService } from "./return-reason"
export { default as SalesChannelService } from "./sales-channel"
export { default as SalesChannelInventoryService } from "./sales-channel-inventory"
export { default as SalesChannelLocationService } from "./sales-channel-location"
export { default as SalesChannelService } from "./sales-channel"
export { default as SearchService } from "./search"
export { default as ShippingOptionService } from "./shipping-option"
export { default as ShippingProfileService } from "./shipping-profile"
export { default as StagedJobService } from "./staged-job"
export { default as StoreService } from "./store"
export { default as StrategyResolverService } from "./strategy-resolver"
export { default as SwapService } from "./swap"
@@ -54,5 +55,4 @@ export { default as TaxProviderService } from "./tax-provider"
export { default as TaxRateService } from "./tax-rate"
export { default as TokenService } from "./token"
export { default as TotalsService } from "./totals"
export { default as NewTotalsService } from "./new-totals"
export { default as UserService } from "./user"
+4 -3
View File
@@ -1,15 +1,16 @@
import jwt, { JwtPayload } from "jsonwebtoken"
import { MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import { EventBusService, UserService } from "."
import { UserService } from "."
import { User } from ".."
import { TransactionBaseService } from "../interfaces"
import { UserRoles } from "../models/user"
import { InviteRepository } from "../repositories/invite"
import { UserRepository } from "../repositories/user"
import { ListInvite } from "../types/invites"
import { ConfigModule } from "../types/global"
import { TransactionBaseService } from "../interfaces"
import { ListInvite } from "../types/invites"
import { buildQuery } from "../utils"
import EventBusService from "./event-bus"
// 7 days
const DEFAULT_VALID_DURATION = 1000 * 60 * 60 * 24 * 7
+23 -27
View File
@@ -1,11 +1,9 @@
import Bull from "bull"
import { Job, Queue, Worker } from "bullmq"
import Redis from "ioredis"
import { ConfigModule, Logger } from "../types/global"
type InjectedDependencies = {
logger: Logger
redisClient: Redis.Redis
redisSubscriber: Redis.Redis
}
type ScheduledJobHandler<T = unknown> = (
@@ -22,36 +20,34 @@ export default class JobSchedulerService {
protected readonly logger_: Logger
protected readonly handlers_: Map<string | symbol, ScheduledJobHandler[]> =
new Map()
protected readonly queue_: Bull
protected readonly queue_: Queue
constructor(
{ logger, redisClient, redisSubscriber }: InjectedDependencies,
{ logger }: InjectedDependencies,
config: ConfigModule,
singleton = true
) {
this.config_ = config
this.logger_ = logger
if (singleton) {
const opts = {
createClient: (type: string): Redis.Redis => {
switch (type) {
case "client":
return redisClient
case "subscriber":
return redisSubscriber
default:
if (config.projectConfig.redis_url) {
return new Redis(config.projectConfig.redis_url)
}
return redisClient
}
},
}
if (singleton && config?.projectConfig?.redis_url) {
// Required config
// See: https://github.com/OptimalBits/bull/blob/develop/CHANGELOG.md#breaking-changes
const connection = new Redis(config.projectConfig.redis_url, {
maxRetriesPerRequest: null,
enableReadyCheck: false,
})
this.queue_ = new Queue(`scheduled-jobs:queue`, {
connection,
prefix: `${this.constructor.name}`,
})
this.queue_ = new Bull(`scheduled-jobs:queue`, opts)
// Register scheduled job worker
this.queue_.process(this.scheduledJobsWorker)
new Worker("scheduled-jobs:queue", this.scheduledJobsWorker, {
connection,
prefix: `${this.constructor.name}`,
})
}
}
@@ -112,7 +108,7 @@ export default class JobSchedulerService {
schedule: string,
handler: ScheduledJobHandler,
options: CreateJobOptions
): Promise<void> {
): Promise<Job> {
this.logger_.info(`Registering ${eventName}`)
this.registerHandler(eventName, handler)
@@ -120,10 +116,10 @@ export default class JobSchedulerService {
eventName,
data,
}
const repeatOpts = { repeat: { cron: schedule } }
const repeatOpts = { repeat: { pattern: schedule } }
if (options?.keepExisting) {
return await this.queue_.add(jobToCreate, repeatOpts)
return await this.queue_.add(eventName, jobToCreate, repeatOpts)
}
const existingJobs = (await this.queue_.getRepeatableJobs()) ?? []
@@ -134,6 +130,6 @@ export default class JobSchedulerService {
await this.queue_.removeRepeatableByKey(existingJob.key)
}
return await this.queue_.add(jobToCreate, repeatOpts)
return await this.queue_.add(eventName, jobToCreate, repeatOpts)
}
}
+4 -4
View File
@@ -1,12 +1,12 @@
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import { TransactionBaseService } from "../interfaces"
import { NoteRepository } from "../repositories/note"
import EventBusService from "./event-bus"
import { FindConfig, Selector } from "../types/common"
import { Note } from "../models"
import { buildQuery } from "../utils"
import { NoteRepository } from "../repositories/note"
import { FindConfig, Selector } from "../types/common"
import { CreateNoteInput } from "../types/note"
import { buildQuery } from "../utils"
import EventBusService from "./event-bus"
type InjectedDependencies = {
manager: EntityManager
@@ -1,18 +1,19 @@
import { TransactionBaseService } from "../interfaces"
import { OrderItemChangeRepository } from "../repositories/order-item-change"
import { EntityManager, In } from "typeorm"
import { EventBusService, LineItemService } from "./index"
import { FindConfig, Selector } from "../types/common"
import { OrderItemChange } from "../models"
import { buildQuery } from "../utils"
import { EventBusTypes } from "@medusajs/types"
import { MedusaError } from "medusa-core-utils"
import TaxProviderService from "./tax-provider"
import { EntityManager, In } from "typeorm"
import { TransactionBaseService } from "../interfaces"
import { OrderItemChange } from "../models"
import { OrderItemChangeRepository } from "../repositories/order-item-change"
import { FindConfig, Selector } from "../types/common"
import { CreateOrderEditItemChangeInput } from "../types/order-edit"
import { buildQuery } from "../utils"
import { LineItemService } from "./index"
import TaxProviderService from "./tax-provider"
type InjectedDependencies = {
manager: EntityManager
orderItemChangeRepository: typeof OrderItemChangeRepository
eventBusService: EventBusService
eventBusService: EventBusTypes.IEventBusService
lineItemService: LineItemService
taxProviderService: TaxProviderService
}
@@ -25,7 +26,7 @@ export default class OrderEditItemChangeService extends TransactionBaseService {
// eslint-disable-next-line max-len
protected readonly orderItemChangeRepository_: typeof OrderItemChangeRepository
protected readonly eventBus_: EventBusService
protected readonly eventBus_: EventBusTypes.IEventBusService
protected readonly lineItemService_: LineItemService
protected readonly taxProviderService_: TaxProviderService
+1 -1
View File
@@ -22,8 +22,8 @@ import {
CreateOrderEditInput,
} from "../types/order-edit"
import { buildQuery, isString } from "../utils"
import EventBusService from "./event-bus"
import {
EventBusService,
LineItemAdjustmentService,
LineItemService,
NewTotalsService,
+4 -2
View File
@@ -8,7 +8,7 @@ import {
Not,
Raw,
} from "typeorm"
import { TransactionBaseService } from "../interfaces"
import { IInventoryService, TransactionBaseService } from "../interfaces"
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
import {
Address,
@@ -50,7 +50,6 @@ import {
CustomerService,
DiscountService,
DraftOrderService,
EventBusService,
FulfillmentProviderService,
FulfillmentService,
GiftCardService,
@@ -64,6 +63,7 @@ import {
TaxProviderService,
TotalsService
} from "."
import EventBusService from "./event-bus"
export const ORDER_CART_ALREADY_EXISTS_ERROR = "Order from cart already exists"
@@ -86,6 +86,7 @@ type InjectedDependencies = {
addressRepository: typeof AddressRepository
giftCardService: GiftCardService
draftOrderService: DraftOrderService
inventoryService: IInventoryService
eventBusService: EventBusService
featureFlagRouter: FlagRouter
productVariantInventoryService: ProductVariantInventoryService
@@ -128,6 +129,7 @@ class OrderService extends TransactionBaseService {
protected readonly addressRepository_: typeof AddressRepository
protected readonly giftCardService_: GiftCardService
protected readonly draftOrderService_: DraftOrderService
protected readonly inventoryService_: IInventoryService
protected readonly eventBus_: EventBusService
protected readonly featureFlagRouter_: FlagRouter
// eslint-disable-next-line max-len
@@ -1,28 +1,25 @@
import { DeepPartial, EntityManager } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils"
import { DeepPartial, EntityManager } from "typeorm"
import { FindConfig } from "../types/common"
import { buildQuery, isString, setMetadata } from "../utils"
import { PaymentCollectionRepository } from "../repositories/payment-collection"
import {
PaymentCollection,
PaymentCollectionStatus,
PaymentSession,
PaymentSessionStatus,
} from "../models"
import { TransactionBaseService } from "../interfaces"
import {
CustomerService,
EventBusService,
PaymentProviderService,
} from "./index"
import { PaymentCollectionRepository } from "../repositories/payment-collection"
import { FindConfig } from "../types/common"
import { buildQuery, isString, setMetadata } from "../utils"
import { CustomerService, PaymentProviderService } from "./index"
import { TransactionBaseService } from "../interfaces"
import { CreatePaymentInput, PaymentSessionInput } from "../types/payment"
import {
CreatePaymentCollectionInput,
PaymentCollectionsSessionsBatchInput,
PaymentCollectionsSessionsInput,
} from "../types/payment-collection"
import { CreatePaymentInput, PaymentSessionInput } from "../types/payment"
import EventBusService from "./event-bus"
type InjectedDependencies = {
manager: EntityManager
+6 -5
View File
@@ -1,12 +1,13 @@
import { PaymentRepository } from "./../repositories/payment"
import { EntityManager } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import { PaymentRepository } from "./../repositories/payment"
import { Payment, Refund } from "../models"
import { TransactionBaseService } from "../interfaces"
import { EventBusService, PaymentProviderService } from "./index"
import { buildQuery } from "../utils"
import { Payment, Refund } from "../models"
import { FindConfig } from "../types/common"
import { buildQuery } from "../utils"
import EventBusService from "./event-bus"
import { PaymentProviderService } from "./index"
type InjectedDependencies = {
manager: EntityManager
@@ -1,23 +1,22 @@
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager, IsNull, MoreThanOrEqual, Between, Not } from "typeorm"
import { Between, EntityManager, MoreThanOrEqual, Not } from "typeorm"
import { EventBusService } from "."
import { TransactionBaseService } from "../interfaces"
import { ProductCategory } from "../models"
import { ProductCategoryRepository } from "../repositories/product-category"
import {
FindConfig,
QuerySelector,
TreeQuerySelector,
Selector,
TreeQuerySelector,
} from "../types/common"
import { buildQuery, nullableValue } from "../utils"
import { EventBusService } from "."
import {
CreateProductCategoryInput,
UpdateProductCategoryInput,
ReorderConditions,
tempReorderRank,
UpdateProductCategoryInput,
} from "../types/product-category"
import { isNumber } from "lodash"
import { buildQuery, nullableValue } from "../utils"
type InjectedDependencies = {
manager: EntityManager
+1 -2
View File
@@ -1,5 +1,3 @@
import { FlagRouter } from "../utils/flag-router"
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import { ProductVariantService, SearchService } from "."
@@ -31,6 +29,7 @@ import {
UpdateProductInput,
} from "../types/product"
import { buildQuery, isString, setMetadata } from "../utils"
import { FlagRouter } from "../utils/flag-router"
import EventBusService from "./event-bus"
type InjectedDependencies = {
@@ -1,17 +1,17 @@
import { EntityManager, FindOptionsWhere, ILike } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager, FindOptionsWhere, ILike } from "typeorm"
import { PublishableApiKeyRepository } from "../repositories/publishable-api-key"
import { FindConfig, Selector } from "../types/common"
import { PublishableApiKey, SalesChannel } from "../models"
import { TransactionBaseService } from "../interfaces"
import EventBusService from "./event-bus"
import { buildQuery, isString } from "../utils"
import { PublishableApiKey, SalesChannel } from "../models"
import { PublishableApiKeyRepository } from "../repositories/publishable-api-key"
import { PublishableApiKeySalesChannelRepository } from "../repositories/publishable-api-key-sales-channel"
import { FindConfig, Selector } from "../types/common"
import {
CreatePublishableApiKeyInput,
UpdatePublishableApiKeyInput,
} from "../types/publishable-api-key"
import { PublishableApiKeySalesChannelRepository } from "../repositories/publishable-api-key-sales-channel"
import { buildQuery, isString } from "../utils"
import EventBusService from "./event-bus"
type InjectedDependencies = {
manager: EntityManager
@@ -1,18 +1,20 @@
import { EntityManager } from "typeorm"
import { EventBusService, SalesChannelLocationService } from "./"
import { IInventoryService, TransactionBaseService } from "../interfaces"
import { EventBusTypes } from "@medusajs/types"
import { SalesChannelLocationService } from "./"
type InjectedDependencies = {
inventoryService: IInventoryService
salesChannelLocationService: SalesChannelLocationService
eventBusService: EventBusService
eventBusService: EventBusTypes.IEventBusService
manager: EntityManager
}
class SalesChannelInventoryService extends TransactionBaseService {
protected readonly salesChannelLocationService_: SalesChannelLocationService
protected readonly eventBusService_: EventBusService
protected readonly eventBusService_: EventBusTypes.IEventBusService
protected readonly inventoryService_: IInventoryService
constructor({
@@ -1,14 +1,16 @@
import { EntityManager, In } from "typeorm"
import { IStockLocationService, TransactionBaseService } from "../interfaces"
import { EventBusService, SalesChannelService } from "./"
import { SalesChannelService } from "./"
import { EventBusTypes } from "@medusajs/types"
import { SalesChannelLocation } from "../models/sales-channel-location"
import { MedusaError } from "medusa-core-utils"
import { SalesChannelLocation } from "../models/sales-channel-location"
type InjectedDependencies = {
stockLocationService: IStockLocationService
salesChannelService: SalesChannelService
eventBusService: EventBusService
eventBusService: EventBusTypes.IEventBusService
manager: EntityManager
}
@@ -18,7 +20,7 @@ type InjectedDependencies = {
class SalesChannelLocationService extends TransactionBaseService {
protected readonly salesChannelService_: SalesChannelService
protected readonly eventBusService_: EventBusService
protected readonly eventBusService_: EventBusTypes.IEventBusService
protected readonly stockLocationService_: IStockLocationService
constructor({
@@ -1,17 +1,17 @@
import { FindConfig, QuerySelector, Selector } from "../types/common"
import {
CreateSalesChannelInput,
UpdateSalesChannelInput,
} from "../types/sales-channels"
import { FindConfig, QuerySelector, Selector } from "../types/common"
import { EntityManager } from "typeorm"
import EventBusService from "./event-bus"
import { isDefined, MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import { TransactionBaseService } from "../interfaces"
import { SalesChannel } from "../models"
import { SalesChannelRepository } from "../repositories/sales-channel"
import StoreService from "./store"
import { TransactionBaseService } from "../interfaces"
import { buildQuery } from "../utils"
import EventBusService from "./event-bus"
import StoreService from "./store"
type InjectedDependencies = {
salesChannelRepository: typeof SalesChannelRepository
@@ -0,0 +1,63 @@
import { EventBusTypes } from "@medusajs/types"
import { DeepPartial, EntityManager, In } from "typeorm"
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
import { TransactionBaseService } from "../interfaces"
import { StagedJob } from "../models"
import { StagedJobRepository } from "../repositories/staged-job"
import { FindConfig } from "../types/common"
import { isString } from "../utils"
type StagedJobServiceProps = {
manager: EntityManager
stagedJobRepository: typeof StagedJobRepository
}
/**
* Provides layer to manipulate users.
*/
class StagedJobService extends TransactionBaseService {
protected stagedJobRepository_: typeof StagedJobRepository
constructor({ stagedJobRepository }: StagedJobServiceProps) {
// eslint-disable-next-line prefer-rest-params
super(arguments[0])
this.stagedJobRepository_ = stagedJobRepository
}
async list(config: FindConfig<StagedJob>) {
const stagedJobRepo = this.activeManager_.withRepository(
this.stagedJobRepository_
)
return await stagedJobRepo.find(config)
}
async delete(stagedJobIds: string | string[]): Promise<void> {
const manager = this.activeManager_
const stagedJobRepo = manager.withRepository(this.stagedJobRepository_)
const sjIds = isString(stagedJobIds) ? [stagedJobIds] : stagedJobIds
await stagedJobRepo.delete({ id: In(sjIds) })
}
async create(data: EventBusTypes.EmitData[] | EventBusTypes.EmitData) {
return await this.atomicPhase_(async (manager) => {
const stagedJobRepo = manager.withRepository(this.stagedJobRepository_)
const data_ = Array.isArray(data) ? data : [data]
const stagedJobs = data_.map((job) =>
stagedJobRepo.create({
event_name: job.eventName,
data: job.data,
options: job.options,
} as DeepPartial<StagedJob>)
) as QueryDeepPartialEntity<StagedJob>[]
return await stagedJobRepo.insertBulk(stagedJobs)
})
}
}
export default StagedJobService
+2 -3
View File
@@ -23,11 +23,12 @@ import { SwapRepository } from "../repositories/swap"
import { FindConfig, Selector, WithRequiredProperty } from "../types/common"
import { CreateShipmentConfig } from "../types/fulfillment"
import { OrdersReturnItem } from "../types/orders"
import CartService from "./cart"
import {
CartService,
CustomShippingOptionService,
EventBusService,
FulfillmentService,
LineItemAdjustmentService,
LineItemService,
OrderService,
PaymentProviderService,
@@ -36,7 +37,6 @@ import {
ShippingOptionService,
TotalsService,
} from "./index"
import LineItemAdjustmentService from "./line-item-adjustment"
type InjectedProps = {
manager: EntityManager
@@ -44,7 +44,6 @@ type InjectedProps = {
swapRepository: typeof SwapRepository
cartService: CartService
eventBus: EventBusService
orderService: OrderService
returnService: ReturnService
totalsService: TotalsService
+12 -12
View File
@@ -1,10 +1,14 @@
import { MedusaError } from "medusa-core-utils"
import { AwilixContainer } from "awilix"
import { MedusaError } from "medusa-core-utils"
import { In } from "typeorm"
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
import { ShippingMethodTaxLineRepository } from "../repositories/shipping-method-tax-line"
import { TaxProviderRepository } from "../repositories/tax-provider"
import {
ICacheService,
ITaxService,
ItemTaxCalculationLine,
TaxCalculationContext,
TransactionBaseService,
} from "../interfaces"
import {
Cart,
LineItem,
@@ -14,19 +18,15 @@ import {
ShippingMethodTaxLine,
TaxProvider,
} from "../models"
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
import { ShippingMethodTaxLineRepository } from "../repositories/shipping-method-tax-line"
import { TaxProviderRepository } from "../repositories/tax-provider"
import { isCart } from "../types/cart"
import {
ICacheService,
ITaxService,
ItemTaxCalculationLine,
TaxCalculationContext,
TransactionBaseService,
} from "../interfaces"
import { TaxLinesMaps, TaxServiceRate } from "../types/tax-service"
import EventBusService from "./event-bus"
import TaxRateService from "./tax-rate"
import EventBusService from "./event-bus"
type RegionDetails = {
id: string
@@ -6,16 +6,15 @@ import {
orderExportPropertiesDescriptors,
} from "."
import { AdminPostBatchesReq } from "../../../api"
import { IFileService } from "../../../interfaces"
import { AbstractBatchJobStrategy } from "../../../interfaces"
import { AbstractBatchJobStrategy, IFileService } from "../../../interfaces"
import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels"
import { Order } from "../../../models"
import { OrderService } from "../../../services"
import BatchJobService from "../../../services/batch-job"
import { BatchJobStatus } from "../../../types/batch-job"
import { prepareListQuery } from "../../../utils/get-query-config"
import { FlagRouter } from "../../../utils/flag-router"
import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels"
import { FindConfig } from "../../../types/common"
import { FlagRouter } from "../../../utils/flag-router"
import { prepareListQuery } from "../../../utils/get-query-config"
type InjectedDependencies = {
fileService: IFileService
+14 -6
View File
@@ -1,7 +1,9 @@
import BatchJobService from "../services/batch-job"
import EventBusService from "../services/event-bus"
import { StrategyResolverService } from "../services"
import { EntityManager } from "typeorm"
import {
BatchJobService,
EventBusService,
StrategyResolverService,
} from "../services"
type InjectedDependencies = {
eventBusService: EventBusService
@@ -27,9 +29,15 @@ class BatchJobSubscriber {
this.strategyResolver_ = strategyResolverService
this.manager_ = manager
this.eventBusService_
.subscribe(BatchJobService.Events.CREATED, this.preProcessBatchJob)
.subscribe(BatchJobService.Events.CONFIRMED, this.processBatchJob)
this.eventBusService_.subscribe(
BatchJobService.Events.CREATED,
this.preProcessBatchJob
) as EventBusService
this.eventBusService_.subscribe(
BatchJobService.Events.CONFIRMED,
this.processBatchJob
) as EventBusService
}
preProcessBatchJob = async (data): Promise<void> => {
+1 -2
View File
@@ -1,6 +1,5 @@
import EventBusService from "../services/event-bus"
import { CartService } from "../services"
import { EntityManager } from "typeorm"
import { CartService, EventBusService } from "../services"
type InjectedDependencies = {
eventBusService: EventBusService
@@ -1,21 +1,21 @@
import { EventBusTypes } from "@medusajs/types"
import { indexTypes } from "medusa-core-utils"
import { ISearchService } from "../interfaces"
import ProductCategoryFeatureFlag from "../loaders/feature-flags/product-categories"
import { SEARCH_INDEX_EVENT } from "../loaders/search-index"
import { Product } from "../models"
import EventBusService from "../services/event-bus"
import ProductService from "../services/product"
import { FlagRouter } from "../utils/flag-router"
type InjectedDependencies = {
eventBusService: EventBusService
eventBusService: EventBusTypes.IEventBusService
searchService: ISearchService
productService: ProductService
featureFlagRouter: FlagRouter
}
class SearchIndexingSubscriber {
private readonly eventBusService_: EventBusService
private readonly eventBusService_: EventBusTypes.IEventBusService
private readonly searchService_: ISearchService
private readonly productService_: ProductService
private readonly featureFlagRouter_: FlagRouter
+2 -70
View File
@@ -1,13 +1,8 @@
import {
ExternalModuleDeclaration,
InternalModuleDeclaration,
} from "@medusajs/modules-sdk"
import { CommonTypes } from "@medusajs/types"
import { Request } from "express"
import { MedusaContainer as coreMedusaContainer } from "medusa-core-utils"
import { LoggerOptions } from "typeorm"
import { Logger as _Logger } from "winston"
import { Customer, User } from "../models"
import { EmitOptions } from "../services/event-bus"
import { FindConfig, RequestQueryFields } from "./common"
declare global {
@@ -44,67 +39,4 @@ export type Logger = _Logger & {
export type Constructor<T> = new (...args: any[]) => T
type SessionOptions = {
name?: string
resave?: boolean
rolling?: boolean
saveUninitialized?: boolean
secret?: string
ttl?: number
}
export type ConfigModule = {
projectConfig: {
redis_url?: string
/**
* Global options passed to all `EventBusService.emit` in the core as well as your own emitters. The options are forwarded to Bull's `Queue.add` method.
*
* The global options can be overridden by passing options to `EventBusService.emit` directly.
*
* Note: This will be deprecated as we move to Event Bus module in 1.8
*
*
* Example
* ```js
* {
* removeOnComplete: { age: 10 },
* }
* ```
*
* @see https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queueadd
*/
event_options?: Record<string, unknown> & EmitOptions
session_options?: SessionOptions
jwt_secret?: string
cookie_secret?: string
database_url?: string
database_type: string
database_database?: string
database_schema?: string
database_logging: LoggerOptions
database_extra?: Record<string, unknown> & {
ssl: { rejectUnauthorized: false }
}
store_cors?: string
admin_cors?: string
}
featureFlags: Record<string, boolean | string>
modules?: Record<
string,
| false
| string
| Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
>
plugins: (
| {
resolve: string
options: Record<string, unknown>
}
| string
)[]
}
export type ConfigModule = CommonTypes.ConfigModule
+3 -5
View File
@@ -1,5 +1,3 @@
export async function sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
import { promisify } from "util"
export const sleep = promisify(setTimeout)