feat(medusa,event-bus-local,event-bus-redis): Event Bus modules (#2599)
This commit is contained in:
committed by
GitHub
parent
7408111d11
commit
ef5ef9f5a2
8
.changeset/three-pans-knock.md
Normal file
8
.changeset/three-pans-knock.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": minor
|
||||||
|
"medusa-react": patch
|
||||||
|
"@medusajs/event-bus-local": minor
|
||||||
|
"@medusajs/event-bus-redis": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(medusa,event-bus-local,event-bus-redis): Event Bus module (Redis + Local)
|
||||||
@@ -10,6 +10,8 @@ packages/*
|
|||||||
!packages/admin-ui
|
!packages/admin-ui
|
||||||
!packages/admin
|
!packages/admin
|
||||||
!packages/medusa-payment-stripe
|
!packages/medusa-payment-stripe
|
||||||
|
!packages/event-bus-redis
|
||||||
|
!packages/event-bus-local
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ module.exports = {
|
|||||||
project: [
|
project: [
|
||||||
"./packages/medusa/tsconfig.json",
|
"./packages/medusa/tsconfig.json",
|
||||||
"./packages/medusa-payment-stripe/tsconfig.spec.json",
|
"./packages/medusa-payment-stripe/tsconfig.spec.json",
|
||||||
|
"./packages/event-bus-local/tsconfig.spec.json",
|
||||||
|
"./packages/event-bus-redis/tsconfig.spec.json",
|
||||||
"./packages/admin-ui/tsconfig.json",
|
"./packages/admin-ui/tsconfig.json",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`/admin/batch-jobs GET /admin/batch-jobs lists batch jobs created by the user 1`] = `
|
|
||||||
Object {
|
|
||||||
"batch_jobs": Array [
|
|
||||||
Object {
|
|
||||||
"canceled_at": null,
|
|
||||||
"completed_at": "2022-06-27T22:00:00.000Z",
|
|
||||||
"confirmed_at": null,
|
|
||||||
"context": Object {},
|
|
||||||
"created_at": Any<String>,
|
|
||||||
"created_by": "admin_user",
|
|
||||||
"deleted_at": null,
|
|
||||||
"dry_run": false,
|
|
||||||
"failed_at": null,
|
|
||||||
"id": "job_5",
|
|
||||||
"pre_processed_at": null,
|
|
||||||
"processing_at": null,
|
|
||||||
"result": null,
|
|
||||||
"status": "completed",
|
|
||||||
"type": "product-export",
|
|
||||||
"updated_at": Any<String>,
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"canceled_at": null,
|
|
||||||
"completed_at": null,
|
|
||||||
"confirmed_at": null,
|
|
||||||
"context": Object {},
|
|
||||||
"created_at": Any<String>,
|
|
||||||
"created_by": "admin_user",
|
|
||||||
"deleted_at": null,
|
|
||||||
"dry_run": false,
|
|
||||||
"failed_at": null,
|
|
||||||
"id": "job_3",
|
|
||||||
"pre_processed_at": null,
|
|
||||||
"processing_at": null,
|
|
||||||
"result": null,
|
|
||||||
"status": "created",
|
|
||||||
"type": "product-export",
|
|
||||||
"updated_at": Any<String>,
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"canceled_at": null,
|
|
||||||
"completed_at": null,
|
|
||||||
"confirmed_at": null,
|
|
||||||
"context": Object {},
|
|
||||||
"created_at": Any<String>,
|
|
||||||
"created_by": "admin_user",
|
|
||||||
"deleted_at": null,
|
|
||||||
"dry_run": false,
|
|
||||||
"failed_at": null,
|
|
||||||
"id": "job_2",
|
|
||||||
"pre_processed_at": null,
|
|
||||||
"processing_at": null,
|
|
||||||
"result": null,
|
|
||||||
"status": "created",
|
|
||||||
"type": "product-export",
|
|
||||||
"updated_at": Any<String>,
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"canceled_at": null,
|
|
||||||
"completed_at": null,
|
|
||||||
"confirmed_at": null,
|
|
||||||
"context": Object {},
|
|
||||||
"created_at": Any<String>,
|
|
||||||
"created_by": "admin_user",
|
|
||||||
"deleted_at": null,
|
|
||||||
"dry_run": false,
|
|
||||||
"failed_at": null,
|
|
||||||
"id": "job_1",
|
|
||||||
"pre_processed_at": null,
|
|
||||||
"processing_at": null,
|
|
||||||
"result": null,
|
|
||||||
"status": "created",
|
|
||||||
"type": "product-export",
|
|
||||||
"updated_at": Any<String>,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"count": 4,
|
|
||||||
"limit": 10,
|
|
||||||
"offset": 0,
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -84,34 +84,36 @@ describe("/admin/batch-jobs", () => {
|
|||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.batch_jobs.length).toEqual(4)
|
expect(response.data.batch_jobs.length).toEqual(4)
|
||||||
expect(response.data).toMatchSnapshot({
|
expect(response.data).toEqual(
|
||||||
batch_jobs: [
|
expect.objectContaining({
|
||||||
{
|
batch_jobs: expect.arrayContaining([
|
||||||
id: "job_5",
|
expect.objectContaining({
|
||||||
created_at: expect.any(String),
|
id: "job_5",
|
||||||
updated_at: expect.any(String),
|
created_at: expect.any(String),
|
||||||
created_by: "admin_user",
|
updated_at: expect.any(String),
|
||||||
},
|
created_by: "admin_user",
|
||||||
{
|
}),
|
||||||
id: "job_3",
|
expect.objectContaining({
|
||||||
created_at: expect.any(String),
|
id: "job_3",
|
||||||
updated_at: expect.any(String),
|
created_at: expect.any(String),
|
||||||
created_by: "admin_user",
|
updated_at: expect.any(String),
|
||||||
},
|
created_by: "admin_user",
|
||||||
{
|
}),
|
||||||
id: "job_2",
|
expect.objectContaining({
|
||||||
created_at: expect.any(String),
|
id: "job_2",
|
||||||
updated_at: expect.any(String),
|
created_at: expect.any(String),
|
||||||
created_by: "admin_user",
|
updated_at: expect.any(String),
|
||||||
},
|
created_by: "admin_user",
|
||||||
{
|
}),
|
||||||
id: "job_1",
|
expect.objectContaining({
|
||||||
created_at: expect.any(String),
|
id: "job_1",
|
||||||
updated_at: expect.any(String),
|
created_at: expect.any(String),
|
||||||
created_by: "admin_user",
|
updated_at: expect.any(String),
|
||||||
},
|
created_by: "admin_user",
|
||||||
],
|
}),
|
||||||
})
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("lists batch jobs created by the user and where completed_at is null ", async () => {
|
it("lists batch jobs created by the user and where completed_at is null ", async () => {
|
||||||
@@ -214,8 +216,6 @@ describe("/admin/batch-jobs", () => {
|
|||||||
created_by: "admin_user",
|
created_by: "admin_user",
|
||||||
status: "created",
|
status: "created",
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
created_at: expect.any(String),
|
|
||||||
updated_at: expect.any(String),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -264,28 +264,6 @@ describe("/admin/batch-jobs", () => {
|
|||||||
await db.teardown()
|
await db.teardown()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Cancels batch job created by the user", async () => {
|
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const jobId = "job_1"
|
|
||||||
|
|
||||||
const response = await api.post(
|
|
||||||
`/admin/batch-jobs/${jobId}/cancel`,
|
|
||||||
{},
|
|
||||||
adminReqConfig
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.batch_job).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
created_at: expect.any(String),
|
|
||||||
updated_at: expect.any(String),
|
|
||||||
canceled_at: expect.any(String),
|
|
||||||
status: "canceled",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Fails to cancel a batch job created by a different user", async () => {
|
it("Fails to cancel a batch job created by a different user", async () => {
|
||||||
expect.assertions(3)
|
expect.assertions(3)
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
@@ -319,5 +297,27 @@ describe("/admin/batch-jobs", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("Cancels batch job created by the user", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const jobId = "job_1"
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/batch-jobs/${jobId}/cancel`,
|
||||||
|
{},
|
||||||
|
adminReqConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.batch_job).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
canceled_at: expect.any(String),
|
||||||
|
status: "canceled",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ describe("Batchjob with type order-export", () => {
|
|||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({
|
medusaProcess = await setupServer({
|
||||||
cwd,
|
cwd,
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ describe("Price list import batch job", () => {
|
|||||||
|
|
||||||
medusaProcess = await setupServer({
|
medusaProcess = await setupServer({
|
||||||
cwd,
|
cwd,
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ describe("Batch job of product-export type", () => {
|
|||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({
|
medusaProcess = await setupServer({
|
||||||
cwd,
|
cwd,
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ const adminSeeder = require("../../../helpers/admin-seeder")
|
|||||||
const userSeeder = require("../../../helpers/user-seeder")
|
const userSeeder = require("../../../helpers/user-seeder")
|
||||||
const { simpleSalesChannelFactory } = require("../../../factories")
|
const { simpleSalesChannelFactory } = require("../../../factories")
|
||||||
const batchJobSeeder = require("../../../helpers/batch-job-seeder")
|
const batchJobSeeder = require("../../../helpers/batch-job-seeder")
|
||||||
const { simpleProductCollectionFactory } = require("../../../factories/simple-product-collection-factory");
|
const {
|
||||||
|
simpleProductCollectionFactory,
|
||||||
|
} = require("../../../factories/simple-product-collection-factory")
|
||||||
|
|
||||||
const startServerWithEnvironment =
|
const startServerWithEnvironment =
|
||||||
require("../../../../helpers/start-server-with-environment").default
|
require("../../../../helpers/start-server-with-environment").default
|
||||||
@@ -52,7 +54,7 @@ describe("Product import - Sales Channel", () => {
|
|||||||
let dbConnection
|
let dbConnection
|
||||||
let medusaProcess
|
let medusaProcess
|
||||||
|
|
||||||
let collectionHandle1 = "test-collection1"
|
const collectionHandle1 = "test-collection1"
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
@@ -62,7 +64,6 @@ describe("Product import - Sales Channel", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
@@ -90,7 +91,7 @@ describe("Product import - Sales Channel", () => {
|
|||||||
name: "Import Sales Channel 2",
|
name: "Import Sales Channel 2",
|
||||||
})
|
})
|
||||||
await simpleProductCollectionFactory(dbConnection, {
|
await simpleProductCollectionFactory(dbConnection, {
|
||||||
handle: collectionHandle1
|
handle: collectionHandle1,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
@@ -169,8 +170,8 @@ describe("Product import - Sales Channel", () => {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
collection: expect.objectContaining({
|
collection: expect.objectContaining({
|
||||||
handle: collectionHandle1
|
handle: collectionHandle1,
|
||||||
})
|
}),
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ describe("Product import batch job", () => {
|
|||||||
|
|
||||||
medusaProcess = await setupServer({
|
medusaProcess = await setupServer({
|
||||||
cwd,
|
cwd,
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -81,7 +80,7 @@ describe("Product import batch job", () => {
|
|||||||
await batchJobSeeder(dbConnection)
|
await batchJobSeeder(dbConnection)
|
||||||
await adminSeeder(dbConnection)
|
await adminSeeder(dbConnection)
|
||||||
await userSeeder(dbConnection)
|
await userSeeder(dbConnection)
|
||||||
await simpleProductCollectionFactory(dbConnection, [
|
await simpleProductCollectionFactory(dbConnection, [
|
||||||
{
|
{
|
||||||
handle: collectionHandle1,
|
handle: collectionHandle1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/cache-inmemory": "*",
|
"@medusajs/cache-inmemory": "*",
|
||||||
|
"@medusajs/event-bus-local": "*",
|
||||||
"@medusajs/medusa": "*",
|
"@medusajs/medusa": "*",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-interfaces": "*",
|
"medusa-interfaces": "*",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { AbstractFileService } from "@medusajs/medusa"
|
import { AbstractFileService } from "@medusajs/medusa"
|
||||||
import stream from "stream"
|
|
||||||
import { resolve } from "path"
|
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
import mkdirp from "mkdirp"
|
import mkdirp from "mkdirp"
|
||||||
|
import { resolve } from "path"
|
||||||
|
import stream from "stream"
|
||||||
|
|
||||||
export default class LocalFileService extends AbstractFileService {
|
export default class LocalFileService extends AbstractFileService {
|
||||||
constructor({}, options) {
|
constructor({}, options) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/cache-inmemory": "*",
|
"@medusajs/cache-inmemory": "*",
|
||||||
|
"@medusajs/event-bus-local": "*",
|
||||||
"@medusajs/medusa": "*",
|
"@medusajs/medusa": "*",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-fulfillment-webshipper": "*",
|
"medusa-fulfillment-webshipper": "*",
|
||||||
|
|||||||
6
packages/event-bus-local/.gitignore
vendored
Normal file
6
packages/event-bus-local/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/dist
|
||||||
|
node_modules
|
||||||
|
.DS_store
|
||||||
|
.env*
|
||||||
|
.env
|
||||||
|
*.sql
|
||||||
63
packages/event-bus-local/README.md
Normal file
63
packages/event-bus-local/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://www.medusajs.com">
|
||||||
|
<img alt="Medusa" src="https://user-images.githubusercontent.com/7554214/153162406-bf8fd16f-aa98-4604-b87b-e13ab4baf604.png" width="100" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<h1 align="center">
|
||||||
|
@medusajs/event-bus-local
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h4 align="center">
|
||||||
|
<a href="https://docs.medusajs.com">Documentation</a> |
|
||||||
|
<a href="https://www.medusajs.com">Website</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
An open source composable commerce engine built for developers.
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/medusajs/medusa/blob/master/LICENSE">
|
||||||
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Medusa is released under the MIT license." />
|
||||||
|
</a>
|
||||||
|
<a href="https://circleci.com/gh/medusajs/medusa">
|
||||||
|
<img src="https://circleci.com/gh/medusajs/medusa.svg?style=shield" alt="Current CircleCI build status." />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
|
||||||
|
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
|
||||||
|
</a>
|
||||||
|
<a href="https://www.producthunt.com/posts/medusa"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-%23DA552E" alt="Product Hunt"></a>
|
||||||
|
<a href="https://discord.gg/xpCwq3Kfn8">
|
||||||
|
<img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/intent/follow?screen_name=medusajs">
|
||||||
|
<img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Local Event Bus module for Medusa. When installed, the events system of Medusa is powered by the Node EventEmitter. This module installed by default in new (> v1.8.0) Medusa projects.
|
||||||
|
|
||||||
|
The Node EventEmitter is limited to a single process environment. We generally recommend using the `@medusajs/event-bus-redis` module in a production environment.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
Install the module:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn add @medusajs/event-bus-local
|
||||||
|
```
|
||||||
|
|
||||||
|
You don't need to add the module to your project configuration as it is the default one. Medusa will try to use it, if no other event buses are installed.
|
||||||
|
|
||||||
|
```js
|
||||||
|
module.exports = {
|
||||||
|
// ...
|
||||||
|
modules: [ ... ],
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The module comes with no configuration options.
|
||||||
13
packages/event-bus-local/jest.config.js
Normal file
13
packages/event-bus-local/jest.config.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
globals: {
|
||||||
|
"ts-jest": {
|
||||||
|
tsConfig: "tsconfig.spec.json",
|
||||||
|
isolatedModules: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
"^.+\\.[jt]s?$": "ts-jest",
|
||||||
|
},
|
||||||
|
testEnvironment: `node`,
|
||||||
|
moduleFileExtensions: [`js`, `jsx`, `ts`, `tsx`, `json`],
|
||||||
|
}
|
||||||
37
packages/event-bus-local/package.json
Normal file
37
packages/event-bus-local/package.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "@medusajs/event-bus-local",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Local Event Bus Module for Medusa",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/medusajs/medusa",
|
||||||
|
"directory": "packages/event-bus-local"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"author": "Medusa",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@medusajs/types": "^0.0.1",
|
||||||
|
"cross-env": "^5.2.1",
|
||||||
|
"jest": "^25.5.2",
|
||||||
|
"ts-jest": "^25.5.1",
|
||||||
|
"typescript": "^4.4.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"watch": "tsc --build --watch",
|
||||||
|
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||||
|
"build": "tsc --build",
|
||||||
|
"test": "jest --passWithNoTests",
|
||||||
|
"test:unit": "jest --passWithNoTests"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@medusajs/modules-sdk": "*",
|
||||||
|
"@medusajs/utils": "^0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
packages/event-bus-local/src/index.ts
Normal file
13
packages/event-bus-local/src/index.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { ModuleExports } from "@medusajs/modules-sdk"
|
||||||
|
import Loader from "./loaders"
|
||||||
|
import LocalEventBus from "./services/event-bus-local"
|
||||||
|
|
||||||
|
export const service = LocalEventBus
|
||||||
|
export const loaders = [Loader]
|
||||||
|
|
||||||
|
const moduleDefinition: ModuleExports = {
|
||||||
|
service,
|
||||||
|
loaders,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default moduleDefinition
|
||||||
8
packages/event-bus-local/src/loaders/index.ts
Normal file
8
packages/event-bus-local/src/loaders/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { LoaderOptions } from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
|
|
||||||
|
export default async ({ logger }: LoaderOptions): Promise<void> => {
|
||||||
|
logger?.warn(
|
||||||
|
"Local Event Bus installed. This is not recommended for production."
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import LocalEventBusService from "../event-bus-local"
|
||||||
|
|
||||||
|
jest.genMockFromModule("events")
|
||||||
|
jest.mock("events")
|
||||||
|
|
||||||
|
const loggerMock = {
|
||||||
|
info: jest.fn().mockReturnValue(console.log),
|
||||||
|
warn: jest.fn().mockReturnValue(console.log),
|
||||||
|
error: jest.fn().mockReturnValue(console.log),
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleDeps = {
|
||||||
|
logger: loggerMock,
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("LocalEventBusService", () => {
|
||||||
|
let eventBus
|
||||||
|
|
||||||
|
describe("emit", () => {
|
||||||
|
describe("Successfully emits events", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Emits an event", () => {
|
||||||
|
eventBus = new LocalEventBusService(
|
||||||
|
moduleDeps,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
resources: "shared",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
eventBus.eventEmitter_.emit.mockImplementationOnce((data) => data)
|
||||||
|
|
||||||
|
eventBus.emit("eventName", { hi: "1234" })
|
||||||
|
|
||||||
|
expect(eventBus.eventEmitter_.emit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(eventBus.eventEmitter_.emit).toHaveBeenCalledWith("eventName", {
|
||||||
|
hi: "1234",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Emits multiple events", () => {
|
||||||
|
eventBus = new LocalEventBusService(
|
||||||
|
moduleDeps,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
resources: "shared",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
eventBus.eventEmitter_.emit.mockImplementationOnce((data) => data)
|
||||||
|
|
||||||
|
eventBus.emit([
|
||||||
|
{ eventName: "event-1", data: { hi: "1234" } },
|
||||||
|
{ eventName: "event-2", data: { hi: "5678" } },
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(eventBus.eventEmitter_.emit).toHaveBeenCalledTimes(2)
|
||||||
|
expect(eventBus.eventEmitter_.emit).toHaveBeenCalledWith("event-1", {
|
||||||
|
hi: "1234",
|
||||||
|
})
|
||||||
|
expect(eventBus.eventEmitter_.emit).toHaveBeenCalledWith("event-2", {
|
||||||
|
hi: "5678",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
80
packages/event-bus-local/src/services/event-bus-local.ts
Normal file
80
packages/event-bus-local/src/services/event-bus-local.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { Logger, MedusaContainer } from "@medusajs/modules-sdk"
|
||||||
|
import { EmitData, Subscriber } from "@medusajs/types"
|
||||||
|
import { AbstractEventBusModuleService } from "@medusajs/utils"
|
||||||
|
import { EventEmitter } from "events"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
logger: Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventEmitter = new EventEmitter()
|
||||||
|
|
||||||
|
export default class LocalEventBusService extends AbstractEventBusModuleService {
|
||||||
|
protected readonly logger_: Logger
|
||||||
|
protected readonly eventEmitter_: EventEmitter
|
||||||
|
|
||||||
|
constructor({ logger }: MedusaContainer & InjectedDependencies) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
|
||||||
|
this.logger_ = logger
|
||||||
|
this.eventEmitter_ = eventEmitter
|
||||||
|
}
|
||||||
|
|
||||||
|
async emit<T>(
|
||||||
|
eventName: string,
|
||||||
|
data: T,
|
||||||
|
options: Record<string, unknown>
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a number of events
|
||||||
|
* @param {EmitData} data - the data to send to the subscriber.
|
||||||
|
*/
|
||||||
|
async emit<T>(data: EmitData<T>[]): Promise<void>
|
||||||
|
|
||||||
|
async emit<T, TInput extends string | EmitData<T>[] = string>(
|
||||||
|
eventOrData: TInput,
|
||||||
|
data?: T,
|
||||||
|
options: Record<string, unknown> = {}
|
||||||
|
): Promise<void> {
|
||||||
|
const isBulkEmit = Array.isArray(eventOrData)
|
||||||
|
|
||||||
|
const events: EmitData[] = isBulkEmit
|
||||||
|
? eventOrData
|
||||||
|
: [{ eventName: eventOrData, data }]
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
const eventListenersCount = this.eventEmitter_.listenerCount(
|
||||||
|
event.eventName
|
||||||
|
)
|
||||||
|
|
||||||
|
this.logger_.info(
|
||||||
|
`Processing ${event.eventName} which has ${eventListenersCount} subscribers`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (eventListenersCount === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.eventEmitter_.emit(event.eventName, event.data)
|
||||||
|
} catch (error) {
|
||||||
|
this.logger_.error(
|
||||||
|
`An error occurred while processing ${event.eventName}: ${error}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(event: string | symbol, subscriber: Subscriber): this {
|
||||||
|
this.eventEmitter_.on(event, subscriber)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(event: string | symbol, subscriber: Subscriber): this {
|
||||||
|
this.eventEmitter_.off(event, subscriber)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
29
packages/event-bus-local/tsconfig.json
Normal file
29
packages/event-bus-local/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es5", "es6", "es2019"],
|
||||||
|
"target": "es6",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"declaration": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"downlevelIteration": true // to use ES5 specific tooling
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"./src/**/__tests__",
|
||||||
|
"./src/**/__mocks__",
|
||||||
|
"./src/**/__fixtures__",
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
5
packages/event-bus-local/tsconfig.spec.json
Normal file
5
packages/event-bus-local/tsconfig.spec.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
6
packages/event-bus-redis/.gitignore
vendored
Normal file
6
packages/event-bus-redis/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/dist
|
||||||
|
node_modules
|
||||||
|
.DS_store
|
||||||
|
.env*
|
||||||
|
.env
|
||||||
|
*.sql
|
||||||
79
packages/event-bus-redis/README.md
Normal file
79
packages/event-bus-redis/README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://www.medusajs.com">
|
||||||
|
<img alt="Medusa" src="https://user-images.githubusercontent.com/7554214/153162406-bf8fd16f-aa98-4604-b87b-e13ab4baf604.png" width="100" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<h1 align="center">
|
||||||
|
@medusajs/event-bus-redis
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h4 align="center">
|
||||||
|
<a href="https://docs.medusajs.com">Documentation</a> |
|
||||||
|
<a href="https://www.medusajs.com">Website</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
An open source composable commerce engine built for developers.
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/medusajs/medusa/blob/master/LICENSE">
|
||||||
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Medusa is released under the MIT license." />
|
||||||
|
</a>
|
||||||
|
<a href="https://circleci.com/gh/medusajs/medusa">
|
||||||
|
<img src="https://circleci.com/gh/medusajs/medusa.svg?style=shield" alt="Current CircleCI build status." />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
|
||||||
|
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
|
||||||
|
</a>
|
||||||
|
<a href="https://www.producthunt.com/posts/medusa"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-%23DA552E" alt="Product Hunt"></a>
|
||||||
|
<a href="https://discord.gg/xpCwq3Kfn8">
|
||||||
|
<img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/intent/follow?screen_name=medusajs">
|
||||||
|
<img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Redis Event Bus module for Medusa. When installed, the events system of Medusa is powered by BullMQ and `io-redis`. BullMQ is responsible for the message queue and worker. `io-redis` is the underlying Redis client, that BullMQ connects to for events storage.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
Install the module:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn add @medusajs/event-bus-redis
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the module to your `medusa-config.js`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
module.exports = {
|
||||||
|
// ...
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
resolve: "@medusajs/event-bus-redis",
|
||||||
|
options: {
|
||||||
|
redisUrl: "redis:.."
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The module can be configured with the following options:
|
||||||
|
|
||||||
|
| Option | Type | Description | Default |
|
||||||
|
| --------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||||
|
| `redisUrl` | `string` | URL of the Redis instance to connect to. | `events-worker` |
|
||||||
|
| `queueName` | `string?` | Name of the BullMQ queue. | `events-queue` |
|
||||||
|
| `queueOptions` | `object?` | Options for the BullMQ queue. See BullMQ's [documentation](https://api.docs.bullmq.io/interfaces/QueueOptions.html). | `{}` |
|
||||||
|
| `redisOptions` | `object?` | Options for the Redis instance. See `io-redis`'s [documentation](https://luin.github.io/ioredis/index.html#RedisOptions) | `{}` |
|
||||||
|
|
||||||
|
**Info**: See how the options are applied in the [RedisEventBusService](https://github.com/medusajs/medusa/blob/0c1d1d590463fa30b083c4312293348bdf6596be/packages/event-bus-redis/src/services/event-bus-redis.ts#L52) and [loader](https://github.com/medusajs/medusa/blob/0c1d1d590463fa30b083c4312293348bdf6596be/packages/event-bus-redis/src/loaders/index.ts).
|
||||||
|
|
||||||
|
If you do not provide a `redisUrl` in the module options, the server will fail to start.
|
||||||
13
packages/event-bus-redis/jest.config.js
Normal file
13
packages/event-bus-redis/jest.config.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
globals: {
|
||||||
|
"ts-jest": {
|
||||||
|
tsConfig: "tsconfig.spec.json",
|
||||||
|
isolatedModules: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
"^.+\\.[jt]s?$": "ts-jest",
|
||||||
|
},
|
||||||
|
testEnvironment: `node`,
|
||||||
|
moduleFileExtensions: [`js`, `jsx`, `ts`, `tsx`, `json`],
|
||||||
|
}
|
||||||
40
packages/event-bus-redis/package.json
Normal file
40
packages/event-bus-redis/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "@medusajs/event-bus-redis",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Redis Event Bus Module for Medusa",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/medusajs/medusa",
|
||||||
|
"directory": "packages/event-bus-redis"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"author": "Medusa",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@medusajs/types": "^0.0.1",
|
||||||
|
"cross-env": "^5.2.1",
|
||||||
|
"jest": "^25.5.2",
|
||||||
|
"medusa-test-utils": "^1.1.39",
|
||||||
|
"ts-jest": "^25.5.1",
|
||||||
|
"typescript": "^4.4.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"watch": "tsc --build --watch",
|
||||||
|
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||||
|
"build": "tsc --build",
|
||||||
|
"test": "jest --passWithNoTests",
|
||||||
|
"test:unit": "jest --passWithNoTests"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@medusajs/modules-sdk": "*",
|
||||||
|
"@medusajs/utils": "^0.0.1",
|
||||||
|
"bullmq": "^3.5.6",
|
||||||
|
"ioredis": "^5.2.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
packages/event-bus-redis/src/index.ts
Normal file
13
packages/event-bus-redis/src/index.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { ModuleExports } from "@medusajs/modules-sdk"
|
||||||
|
import Loader from "./loaders"
|
||||||
|
import RedisEventBusService from "./services/event-bus-redis"
|
||||||
|
|
||||||
|
const service = RedisEventBusService
|
||||||
|
const loaders = [Loader]
|
||||||
|
|
||||||
|
const moduleDefinition: ModuleExports = {
|
||||||
|
service,
|
||||||
|
loaders,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default moduleDefinition
|
||||||
41
packages/event-bus-redis/src/loaders/index.ts
Normal file
41
packages/event-bus-redis/src/loaders/index.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { LoaderOptions } from "@medusajs/modules-sdk"
|
||||||
|
import { asValue } from "awilix"
|
||||||
|
import Redis from "ioredis"
|
||||||
|
import { EOL } from "os"
|
||||||
|
import { EventBusRedisModuleOptions } from "../types"
|
||||||
|
|
||||||
|
export default async ({
|
||||||
|
container,
|
||||||
|
logger,
|
||||||
|
options,
|
||||||
|
}: LoaderOptions): Promise<void> => {
|
||||||
|
const { redisUrl, redisOptions } = options as EventBusRedisModuleOptions
|
||||||
|
|
||||||
|
if (!redisUrl) {
|
||||||
|
throw Error(
|
||||||
|
"No `redis_url` provided in project config. It is required for the Redis Event Bus."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = new Redis(redisUrl, {
|
||||||
|
// Required config. See: https://github.com/OptimalBits/bull/blob/develop/CHANGELOG.md#breaking-changes
|
||||||
|
maxRetriesPerRequest: null,
|
||||||
|
enableReadyCheck: false,
|
||||||
|
// Lazy connect to properly handle connection errors
|
||||||
|
lazyConnect: true,
|
||||||
|
...(redisOptions ?? {}),
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connection.connect()
|
||||||
|
logger?.info(`Connection to Redis in module 'event-bus-redis' established`)
|
||||||
|
} catch (err) {
|
||||||
|
logger?.error(
|
||||||
|
`An error occurred while connecting to Redis in module 'event-bus-redis':${EOL} ${err}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
container.register({
|
||||||
|
eventBusRedisConnection: asValue(connection),
|
||||||
|
})
|
||||||
|
}
|
||||||
319
packages/event-bus-redis/src/services/__tests__/event-bus.js
Normal file
319
packages/event-bus-redis/src/services/__tests__/event-bus.js
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
import { Queue, Worker } from "bullmq"
|
||||||
|
import { MockManager } from "medusa-test-utils"
|
||||||
|
import RedisEventBusService from "../event-bus-redis"
|
||||||
|
|
||||||
|
jest.genMockFromModule("bullmq")
|
||||||
|
jest.genMockFromModule("ioredis")
|
||||||
|
jest.mock("bullmq")
|
||||||
|
jest.mock("ioredis")
|
||||||
|
|
||||||
|
const loggerMock = {
|
||||||
|
info: jest.fn().mockReturnValue(console.log),
|
||||||
|
warn: jest.fn().mockReturnValue(console.log),
|
||||||
|
error: jest.fn().mockReturnValue(console.log),
|
||||||
|
}
|
||||||
|
|
||||||
|
const simpleModuleOptions = { redisUrl: "test-url" }
|
||||||
|
const moduleDeps = {
|
||||||
|
manager: MockManager,
|
||||||
|
logger: loggerMock,
|
||||||
|
eventBusRedisConnection: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("RedisEventBusService", () => {
|
||||||
|
let eventBus
|
||||||
|
|
||||||
|
describe("constructor", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Creates a queue + worker", () => {
|
||||||
|
eventBus = new RedisEventBusService(moduleDeps, simpleModuleOptions, {
|
||||||
|
resources: "shared",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(Queue).toHaveBeenCalledTimes(1)
|
||||||
|
expect(Queue).toHaveBeenCalledWith("events-queue", {
|
||||||
|
connection: expect.any(Object),
|
||||||
|
prefix: "RedisEventBusService",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(Worker).toHaveBeenCalledTimes(1)
|
||||||
|
expect(Worker).toHaveBeenCalledWith(
|
||||||
|
"events-queue",
|
||||||
|
expect.any(Function),
|
||||||
|
{
|
||||||
|
connection: expect.any(Object),
|
||||||
|
prefix: "RedisEventBusService",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Throws on isolated module declaration", () => {
|
||||||
|
try {
|
||||||
|
eventBus = new RedisEventBusService(moduleDeps, simpleModuleOptions, {
|
||||||
|
resources: "isolated",
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toEqual(
|
||||||
|
"At the moment this module can only be used with shared resources"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("emit", () => {
|
||||||
|
describe("Successfully emits events", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Adds job to queue with default options", () => {
|
||||||
|
eventBus = new RedisEventBusService(moduleDeps, simpleModuleOptions, {
|
||||||
|
resources: "shared",
|
||||||
|
})
|
||||||
|
|
||||||
|
eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
|
||||||
|
eventBus.emit("eventName", { hi: "1234" })
|
||||||
|
|
||||||
|
expect(eventBus.queue_.addBulk).toHaveBeenCalledTimes(1)
|
||||||
|
expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
name: "eventName",
|
||||||
|
data: { eventName: "eventName", data: { hi: "1234" } },
|
||||||
|
opts: {
|
||||||
|
attempts: 1,
|
||||||
|
removeOnComplete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Adds job to queue with custom options passed directly upon emitting", () => {
|
||||||
|
eventBus = new RedisEventBusService(moduleDeps, simpleModuleOptions, {
|
||||||
|
resources: "shared",
|
||||||
|
})
|
||||||
|
|
||||||
|
eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
|
||||||
|
eventBus.emit(
|
||||||
|
"eventName",
|
||||||
|
{ hi: "1234" },
|
||||||
|
{ attempts: 3, backoff: 5000, delay: 1000 }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(eventBus.queue_.addBulk).toHaveBeenCalledTimes(1)
|
||||||
|
expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
name: "eventName",
|
||||||
|
data: { eventName: "eventName", data: { hi: "1234" } },
|
||||||
|
opts: {
|
||||||
|
attempts: 3,
|
||||||
|
backoff: 5000,
|
||||||
|
delay: 1000,
|
||||||
|
removeOnComplete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Adds job to queue with module job options", () => {
|
||||||
|
eventBus = new RedisEventBusService(
|
||||||
|
moduleDeps,
|
||||||
|
{
|
||||||
|
...simpleModuleOptions,
|
||||||
|
jobOptions: {
|
||||||
|
removeOnComplete: {
|
||||||
|
age: 5,
|
||||||
|
},
|
||||||
|
attempts: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resources: "shared",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
|
||||||
|
eventBus.emit("eventName", { hi: "1234" })
|
||||||
|
|
||||||
|
expect(eventBus.queue_.addBulk).toHaveBeenCalledTimes(1)
|
||||||
|
expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
name: "eventName",
|
||||||
|
data: { eventName: "eventName", data: { hi: "1234" } },
|
||||||
|
opts: {
|
||||||
|
attempts: 7,
|
||||||
|
removeOnComplete: {
|
||||||
|
age: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Adds job to queue with default, local, and global options merged", () => {
|
||||||
|
eventBus = new RedisEventBusService(
|
||||||
|
moduleDeps,
|
||||||
|
{
|
||||||
|
...simpleModuleOptions,
|
||||||
|
jobOptions: {
|
||||||
|
removeOnComplete: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resources: "shared",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
eventBus.queue_.addBulk.mockImplementationOnce(() => "hi")
|
||||||
|
eventBus.emit("eventName", { hi: "1234" }, { delay: 1000 })
|
||||||
|
|
||||||
|
expect(eventBus.queue_.addBulk).toHaveBeenCalledTimes(1)
|
||||||
|
expect(eventBus.queue_.addBulk).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
name: "eventName",
|
||||||
|
data: { eventName: "eventName", data: { hi: "1234" } },
|
||||||
|
opts: {
|
||||||
|
attempts: 1,
|
||||||
|
removeOnComplete: 5,
|
||||||
|
delay: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("worker_", () => {
|
||||||
|
let result
|
||||||
|
|
||||||
|
describe("Successfully processes the jobs", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
|
||||||
|
eventBus = new RedisEventBusService(moduleDeps, simpleModuleOptions, {
|
||||||
|
resources: "shared",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Processes a simple event with no options", async () => {
|
||||||
|
eventBus.subscribe("eventName", () => Promise.resolve("hi"))
|
||||||
|
|
||||||
|
result = await eventBus.worker_({
|
||||||
|
data: { eventName: "eventName", data: {} },
|
||||||
|
opts: { attempts: 1 },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledTimes(1)
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledWith(
|
||||||
|
"Processing eventName which has 1 subscribers"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toEqual(["hi"])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Processes event with failing subscribers", async () => {
|
||||||
|
eventBus.subscribe("eventName", () => Promise.resolve("hi"))
|
||||||
|
eventBus.subscribe("eventName", () => Promise.reject("fail1"))
|
||||||
|
eventBus.subscribe("eventName", () => Promise.resolve("hi2"))
|
||||||
|
eventBus.subscribe("eventName", () => Promise.reject("fail2"))
|
||||||
|
|
||||||
|
result = await eventBus.worker_({
|
||||||
|
data: { eventName: "eventName", data: {} },
|
||||||
|
update: (data) => data,
|
||||||
|
opts: { attempts: 1 },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledTimes(1)
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledWith(
|
||||||
|
"Processing eventName which has 4 subscribers"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(loggerMock.warn).toHaveBeenCalledTimes(3)
|
||||||
|
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(
|
||||||
|
"One or more subscribers of eventName failed. Retrying is not configured. Use 'attempts' option when emitting events."
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toEqual(["hi", "fail1", "hi2", "fail2"])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Retries processing when subcribers fail, if configured - final attempt", async () => {
|
||||||
|
eventBus.subscribe("eventName", async () => Promise.resolve("hi"), {
|
||||||
|
subscriberId: "1",
|
||||||
|
})
|
||||||
|
eventBus.subscribe("eventName", async () => Promise.reject("fail1"), {
|
||||||
|
subscriberId: "2",
|
||||||
|
})
|
||||||
|
|
||||||
|
result = await eventBus
|
||||||
|
.worker_({
|
||||||
|
data: {
|
||||||
|
eventName: "eventName",
|
||||||
|
data: {},
|
||||||
|
completedSubscriberIds: ["1"],
|
||||||
|
},
|
||||||
|
attemptsMade: 2,
|
||||||
|
update: (data) => data,
|
||||||
|
opts: { attempts: 2 },
|
||||||
|
})
|
||||||
|
.catch((error) => void 0)
|
||||||
|
|
||||||
|
expect(loggerMock.warn).toHaveBeenCalledTimes(1)
|
||||||
|
expect(loggerMock.warn).toHaveBeenCalledWith(
|
||||||
|
"An error occurred while processing eventName: fail1"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledTimes(2)
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledWith(
|
||||||
|
"Final retry attempt for eventName"
|
||||||
|
)
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledWith(
|
||||||
|
"Retrying eventName which has 2 subscribers (1 of them failed)"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Retries processing when subcribers fail, if configured", async () => {
|
||||||
|
eventBus.subscribe("eventName", async () => Promise.resolve("hi"), {
|
||||||
|
subscriberId: "1",
|
||||||
|
})
|
||||||
|
eventBus.subscribe("eventName", async () => Promise.reject("fail1"), {
|
||||||
|
subscriberId: "2",
|
||||||
|
})
|
||||||
|
|
||||||
|
result = await eventBus
|
||||||
|
.worker_({
|
||||||
|
data: {
|
||||||
|
eventName: "eventName",
|
||||||
|
data: {},
|
||||||
|
completedSubscriberIds: ["1"],
|
||||||
|
},
|
||||||
|
attemptsMade: 2,
|
||||||
|
update: (data) => data,
|
||||||
|
opts: { attempts: 3 },
|
||||||
|
})
|
||||||
|
.catch((err) => void 0)
|
||||||
|
|
||||||
|
expect(loggerMock.warn).toHaveBeenCalledTimes(2)
|
||||||
|
expect(loggerMock.warn).toHaveBeenCalledWith(
|
||||||
|
"An error occurred while processing eventName: fail1"
|
||||||
|
)
|
||||||
|
expect(loggerMock.warn).toHaveBeenCalledWith(
|
||||||
|
"One or more subscribers of eventName failed. Retrying..."
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledTimes(1)
|
||||||
|
expect(loggerMock.info).toHaveBeenCalledWith(
|
||||||
|
"Retrying eventName which has 2 subscribers (1 of them failed)"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
209
packages/event-bus-redis/src/services/event-bus-redis.ts
Normal file
209
packages/event-bus-redis/src/services/event-bus-redis.ts
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import { InternalModuleDeclaration, Logger } from "@medusajs/modules-sdk"
|
||||||
|
import { ConfigModule, EmitData } from "@medusajs/types"
|
||||||
|
import { AbstractEventBusModuleService } from "@medusajs/utils"
|
||||||
|
import { BulkJobOptions, JobsOptions, Queue, Worker } from "bullmq"
|
||||||
|
import { Redis } from "ioredis"
|
||||||
|
import { BullJob, EmitOptions, EventBusRedisModuleOptions } from "../types"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
logger: Logger
|
||||||
|
configModule: ConfigModule
|
||||||
|
eventBusRedisConnection: Redis
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can keep track of multiple subscribers to different events and run the
|
||||||
|
* subscribers when events happen. Events will run asynchronously.
|
||||||
|
*/
|
||||||
|
export default class RedisEventBusService extends AbstractEventBusModuleService {
|
||||||
|
protected readonly config_: ConfigModule
|
||||||
|
protected readonly logger_: Logger
|
||||||
|
protected readonly moduleOptions_: EventBusRedisModuleOptions
|
||||||
|
protected readonly moduleDeclaration_: InternalModuleDeclaration
|
||||||
|
|
||||||
|
protected queue_: Queue
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
{ configModule, logger, eventBusRedisConnection }: InjectedDependencies,
|
||||||
|
moduleOptions: EventBusRedisModuleOptions = {},
|
||||||
|
moduleDeclaration: InternalModuleDeclaration
|
||||||
|
) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
|
||||||
|
this.moduleOptions_ = moduleOptions
|
||||||
|
this.config_ = configModule
|
||||||
|
this.logger_ = logger
|
||||||
|
|
||||||
|
this.queue_ = new Queue(moduleOptions.queueName ?? `events-queue`, {
|
||||||
|
prefix: `${this.constructor.name}`,
|
||||||
|
...(moduleOptions.queueOptions ?? {}),
|
||||||
|
connection: eventBusRedisConnection,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Register our worker to handle emit calls
|
||||||
|
new Worker(moduleOptions.queueName ?? "events-queue", this.worker_, {
|
||||||
|
prefix: `${this.constructor.name}`,
|
||||||
|
...(moduleOptions.workerOptions ?? {}),
|
||||||
|
connection: eventBusRedisConnection,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a single event
|
||||||
|
* @param {string} eventName - the name of the event to be process.
|
||||||
|
* @param data - the data to send to the subscriber.
|
||||||
|
* @param options - options to add the job with
|
||||||
|
*/
|
||||||
|
async emit<T>(
|
||||||
|
eventName: string,
|
||||||
|
data: T,
|
||||||
|
options: Record<string, unknown>
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a number of events
|
||||||
|
* @param {EmitData} data - the data to send to the subscriber.
|
||||||
|
*/
|
||||||
|
async emit<T>(data: EmitData<T>[]): Promise<void>
|
||||||
|
|
||||||
|
async emit<T, TInput extends string | EmitData<T>[] = string>(
|
||||||
|
eventNameOrData: TInput,
|
||||||
|
data?: T,
|
||||||
|
options: BulkJobOptions | JobsOptions = {}
|
||||||
|
): Promise<void> {
|
||||||
|
const globalJobOptions = this.moduleOptions_.jobOptions ?? {}
|
||||||
|
|
||||||
|
const isBulkEmit = Array.isArray(eventNameOrData)
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
// default options
|
||||||
|
removeOnComplete: true,
|
||||||
|
attempts: 1,
|
||||||
|
// global options
|
||||||
|
...globalJobOptions,
|
||||||
|
} as EmitOptions
|
||||||
|
|
||||||
|
const events = isBulkEmit
|
||||||
|
? eventNameOrData.map((event) => ({
|
||||||
|
name: event.eventName,
|
||||||
|
data: { eventName: event.eventName, data: event.data },
|
||||||
|
opts: {
|
||||||
|
...opts,
|
||||||
|
// local options
|
||||||
|
...event.options,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
name: eventNameOrData as string,
|
||||||
|
data: { eventName: eventNameOrData, data },
|
||||||
|
opts: {
|
||||||
|
...opts,
|
||||||
|
// local options
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
await this.queue_.addBulk(events)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 currentAttempt = job.attemptsMade
|
||||||
|
const isRetry = currentAttempt > 1
|
||||||
|
const configuredAttempts = job.opts.attempts
|
||||||
|
|
||||||
|
const isFinalAttempt = currentAttempt === configuredAttempts
|
||||||
|
|
||||||
|
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 await subscriber(data, eventName)
|
||||||
|
.then(async (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 = configuredAttempts! > 1
|
||||||
|
|
||||||
|
// Therefore, if retrying is configured, we try again
|
||||||
|
const shouldRetry =
|
||||||
|
didSubscribersFail && isRetriesConfigured && !isFinalAttempt
|
||||||
|
|
||||||
|
if (shouldRetry) {
|
||||||
|
const updatedCompletedSubscribers = [
|
||||||
|
...completedSubscribers,
|
||||||
|
...completedSubscribersInCurrentAttempt,
|
||||||
|
]
|
||||||
|
|
||||||
|
job.data.completedSubscriberIds = updatedCompletedSubscribers
|
||||||
|
|
||||||
|
await 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
40
packages/event-bus-redis/src/types/index.ts
Normal file
40
packages/event-bus-redis/src/types/index.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Job, JobsOptions, QueueOptions, WorkerOptions } from "bullmq"
|
||||||
|
import { RedisOptions } from "ioredis"
|
||||||
|
|
||||||
|
export type JobData<T> = {
|
||||||
|
eventName: string
|
||||||
|
data: T
|
||||||
|
completedSubscriberIds?: string[] | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BullJob<T> = {
|
||||||
|
data: JobData<T>
|
||||||
|
} & Job
|
||||||
|
|
||||||
|
export type EmitOptions = JobsOptions
|
||||||
|
|
||||||
|
export type EventBusRedisModuleOptions = {
|
||||||
|
queueName?: string
|
||||||
|
queueOptions?: QueueOptions
|
||||||
|
|
||||||
|
workerOptions?: WorkerOptions
|
||||||
|
|
||||||
|
redisUrl?: string
|
||||||
|
redisOptions?: RedisOptions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Example
|
||||||
|
* ```js
|
||||||
|
* {
|
||||||
|
* removeOnComplete: { age: 10 },
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @see https://api.docs.bullmq.io/interfaces/BaseJobOptions.html
|
||||||
|
*/
|
||||||
|
jobOptions?: EmitOptions
|
||||||
|
}
|
||||||
29
packages/event-bus-redis/tsconfig.json
Normal file
29
packages/event-bus-redis/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es2020"],
|
||||||
|
"target": "es2020",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"declaration": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"downlevelIteration": true // to use ES5 specific tooling
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"./src/**/__tests__",
|
||||||
|
"./src/**/__mocks__",
|
||||||
|
"./src/**/__fixtures__",
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
5
packages/event-bus-redis/tsconfig.spec.json
Normal file
5
packages/event-bus-redis/tsconfig.spec.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import { IEventBusService, IInventoryService } from "@medusajs/medusa"
|
import { IInventoryService } from "@medusajs/medusa"
|
||||||
import {
|
import {
|
||||||
ExternalModuleDeclaration,
|
ExternalModuleDeclaration,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
MedusaModule,
|
MedusaModule
|
||||||
} from "@medusajs/modules-sdk"
|
} from "@medusajs/modules-sdk"
|
||||||
|
import { EventBusTypes } from "@medusajs/types"
|
||||||
import { InventoryServiceInitializeOptions } from "../types"
|
import { InventoryServiceInitializeOptions } from "../types"
|
||||||
|
|
||||||
export const initialize = async (
|
export const initialize = async (
|
||||||
options?: InventoryServiceInitializeOptions | ExternalModuleDeclaration,
|
options?: InventoryServiceInitializeOptions | ExternalModuleDeclaration,
|
||||||
injectedDependencies?: {
|
injectedDependencies?: {
|
||||||
eventBusService: IEventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
}
|
}
|
||||||
): Promise<IInventoryService> => {
|
): Promise<IInventoryService> => {
|
||||||
const serviceKey = "inventoryService"
|
const serviceKey = "inventoryService"
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import {
|
|||||||
CreateInventoryItemInput,
|
CreateInventoryItemInput,
|
||||||
FilterableInventoryItemProps,
|
FilterableInventoryItemProps,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
IEventBusService,
|
InventoryItemDTO
|
||||||
InventoryItemDTO,
|
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { SharedContext } from "@medusajs/types"
|
import { EventBusTypes, SharedContext } from "@medusajs/types"
|
||||||
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { DeepPartial, EntityManager, FindManyOptions } from "typeorm"
|
import { DeepPartial, EntityManager, FindManyOptions } from "typeorm"
|
||||||
@@ -14,7 +13,7 @@ import { InventoryItem } from "../models"
|
|||||||
import { getListQuery } from "../utils/query"
|
import { getListQuery } from "../utils/query"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
eventBusService: IEventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ export default class InventoryItemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected readonly manager_: EntityManager
|
protected readonly manager_: EntityManager
|
||||||
protected readonly eventBusService_: IEventBusService | undefined
|
protected readonly eventBusService_: EventBusTypes.IEventBusService | undefined
|
||||||
|
|
||||||
constructor({ eventBusService, manager }: InjectedDependencies) {
|
constructor({ eventBusService, manager }: InjectedDependencies) {
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
|
|||||||
@@ -2,17 +2,16 @@ import {
|
|||||||
buildQuery,
|
buildQuery,
|
||||||
CreateInventoryLevelInput,
|
CreateInventoryLevelInput,
|
||||||
FilterableInventoryLevelProps,
|
FilterableInventoryLevelProps,
|
||||||
FindConfig,
|
FindConfig
|
||||||
IEventBusService,
|
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { SharedContext } from "@medusajs/types"
|
import { EventBusTypes, SharedContext } from "@medusajs/types"
|
||||||
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { DeepPartial, EntityManager, FindManyOptions, In } from "typeorm"
|
import { DeepPartial, EntityManager, FindManyOptions, In } from "typeorm"
|
||||||
import { InventoryLevel } from "../models"
|
import { InventoryLevel } from "../models"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
eventBusService: IEventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ export default class InventoryLevelService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected readonly manager_: EntityManager
|
protected readonly manager_: EntityManager
|
||||||
protected readonly eventBusService_: IEventBusService | undefined
|
protected readonly eventBusService_: EventBusTypes.IEventBusService | undefined
|
||||||
|
|
||||||
constructor({ eventBusService, manager }: InjectedDependencies) {
|
constructor({ eventBusService, manager }: InjectedDependencies) {
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
|
|||||||
@@ -8,34 +8,33 @@ import {
|
|||||||
FilterableInventoryLevelProps,
|
FilterableInventoryLevelProps,
|
||||||
FilterableReservationItemProps,
|
FilterableReservationItemProps,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
IEventBusService,
|
|
||||||
IInventoryService,
|
IInventoryService,
|
||||||
InventoryItemDTO,
|
InventoryItemDTO,
|
||||||
InventoryLevelDTO,
|
InventoryLevelDTO,
|
||||||
ReservationItemDTO,
|
ReservationItemDTO,
|
||||||
UpdateInventoryLevelInput,
|
UpdateInventoryLevelInput,
|
||||||
UpdateReservationItemInput,
|
UpdateReservationItemInput
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { SharedContext } from "@medusajs/types"
|
import { EventBusTypes, SharedContext } from "@medusajs/types"
|
||||||
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import {
|
import {
|
||||||
InventoryItemService,
|
InventoryItemService,
|
||||||
InventoryLevelService,
|
InventoryLevelService,
|
||||||
ReservationItemService,
|
ReservationItemService
|
||||||
} from "./"
|
} from "./"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
eventBusService: IEventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
inventoryItemService: InventoryItemService
|
inventoryItemService: InventoryItemService
|
||||||
inventoryLevelService: InventoryLevelService
|
inventoryLevelService: InventoryLevelService
|
||||||
reservationItemService: ReservationItemService
|
reservationItemService: ReservationItemService
|
||||||
}
|
}
|
||||||
export default class InventoryService implements IInventoryService {
|
export default class InventoryService implements IInventoryService {
|
||||||
protected readonly manager_: EntityManager
|
protected readonly manager_: EntityManager
|
||||||
protected readonly eventBusService_: IEventBusService | undefined
|
protected readonly eventBusService_: EventBusTypes.IEventBusService | undefined
|
||||||
protected readonly inventoryItemService_: InventoryItemService
|
protected readonly inventoryItemService_: InventoryItemService
|
||||||
protected readonly reservationItemService_: ReservationItemService
|
protected readonly reservationItemService_: ReservationItemService
|
||||||
protected readonly inventoryLevelService_: InventoryLevelService
|
protected readonly inventoryLevelService_: InventoryLevelService
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import {
|
|||||||
buildQuery,
|
buildQuery,
|
||||||
CreateReservationItemInput,
|
CreateReservationItemInput,
|
||||||
FilterableReservationItemProps,
|
FilterableReservationItemProps,
|
||||||
FindConfig,
|
FindConfig, UpdateReservationItemInput
|
||||||
IEventBusService,
|
|
||||||
UpdateReservationItemInput,
|
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { SharedContext } from "@medusajs/types"
|
import { EventBusTypes, SharedContext } from "@medusajs/types"
|
||||||
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager, FindManyOptions } from "typeorm"
|
import { EntityManager, FindManyOptions } from "typeorm"
|
||||||
@@ -14,7 +12,7 @@ import { InventoryLevelService } from "."
|
|||||||
import { ReservationItem } from "../models"
|
import { ReservationItem } from "../models"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
eventBusService: IEventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
inventoryLevelService: InventoryLevelService
|
inventoryLevelService: InventoryLevelService
|
||||||
}
|
}
|
||||||
@@ -27,7 +25,7 @@ export default class ReservationItemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected readonly manager_: EntityManager
|
protected readonly manager_: EntityManager
|
||||||
protected readonly eventBusService_: IEventBusService | undefined
|
protected readonly eventBusService_: EventBusTypes.IEventBusService | undefined
|
||||||
protected readonly inventoryLevelService_: InventoryLevelService
|
protected readonly inventoryLevelService_: InventoryLevelService
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"@babel/cli": "^7.14.3",
|
"@babel/cli": "^7.14.3",
|
||||||
"@babel/core": "^7.14.3",
|
"@babel/core": "^7.14.3",
|
||||||
"@babel/preset-typescript": "^7.13.0",
|
"@babel/preset-typescript": "^7.13.0",
|
||||||
|
"@medusajs/types": "*",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/jsonwebtoken": "^8.5.9",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
@@ -51,11 +52,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/medusa-cli": "^1.3.8",
|
"@medusajs/medusa-cli": "^1.3.8",
|
||||||
"@medusajs/modules-sdk": "*",
|
"@medusajs/modules-sdk": "*",
|
||||||
|
"@medusajs/utils": "*",
|
||||||
"@types/ioredis": "^4.28.10",
|
"@types/ioredis": "^4.28.10",
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
"awilix": "^8.0.0",
|
"awilix": "^8.0.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"bull": "^3.12.1",
|
"bullmq": "^3.5.6",
|
||||||
"chokidar": "^3.4.2",
|
"chokidar": "^3.4.2",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
@@ -68,7 +70,7 @@
|
|||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"fs-exists-cached": "^1.0.0",
|
"fs-exists-cached": "^1.0.0",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
"ioredis": "^4.17.3",
|
"ioredis": "^5.2.5",
|
||||||
"ioredis-mock": "^5.6.0",
|
"ioredis-mock": "^5.6.0",
|
||||||
"iso8601-duration": "^1.3.0",
|
"iso8601-duration": "^1.3.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { request } from "../../../../../helpers/test-request"
|
|||||||
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
|
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
|
||||||
import {
|
import {
|
||||||
defaultOrderEditFields,
|
defaultOrderEditFields,
|
||||||
defaultOrderEditRelations
|
defaultOrderEditRelations,
|
||||||
} from "../../../../../types/order-edit"
|
} from "../../../../../types/order-edit"
|
||||||
|
|
||||||
describe("GET /admin/order-edits/:id", () => {
|
describe("GET /admin/order-edits/:id", () => {
|
||||||
|
|||||||
@@ -4,22 +4,20 @@ import {
|
|||||||
IsInt,
|
IsInt,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
ValidateNested,
|
ValidateNested
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import {
|
import {
|
||||||
EventBusService,
|
EventBusService,
|
||||||
OrderService,
|
OrderService,
|
||||||
ReturnService,
|
ReturnService
|
||||||
} from "../../../../services"
|
} from "../../../../services"
|
||||||
|
|
||||||
import { Type } from "class-transformer"
|
import { Type } from "class-transformer"
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { Order, Return } from "../../../../models"
|
import { Order, Return } from "../../../../models"
|
||||||
import { OrdersReturnItem } from "../../../../types/orders"
|
|
||||||
import { FindParams } from "../../../../types/common"
|
import { FindParams } from "../../../../types/common"
|
||||||
import { FlagRouter } from "../../../../utils/flag-router"
|
import { OrdersReturnItem } from "../../../../types/orders"
|
||||||
import { IInventoryService } from "../../../../interfaces"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /admin/orders/{id}/return
|
* @oas [post] /admin/orders/{id}/return
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { IsNotEmpty, IsString } from "class-validator"
|
import { IsNotEmpty, IsString } from "class-validator"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import {
|
import { CustomerService, OrderService } from "../../../../services"
|
||||||
CustomerService,
|
import EventBusService from "../../../../services/event-bus"
|
||||||
EventBusService,
|
|
||||||
OrderService,
|
|
||||||
} from "../../../../services"
|
|
||||||
import TokenService from "../../../../services/token"
|
import TokenService from "../../../../services/token"
|
||||||
import { TokenEvents } from "../../../../types/token"
|
import { TokenEvents } from "../../../../types/token"
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
moduleHelper,
|
||||||
|
moduleLoader,
|
||||||
|
registerModules,
|
||||||
|
} from "@medusajs/modules-sdk"
|
||||||
import { asValue, createContainer } from "awilix"
|
import { asValue, createContainer } from "awilix"
|
||||||
import express from "express"
|
import express from "express"
|
||||||
import jwt from "jsonwebtoken"
|
import jwt from "jsonwebtoken"
|
||||||
@@ -7,11 +12,6 @@ import "reflect-metadata"
|
|||||||
import supertest from "supertest"
|
import supertest from "supertest"
|
||||||
import apiLoader from "../loaders/api"
|
import apiLoader from "../loaders/api"
|
||||||
import featureFlagLoader, { featureFlagRouter } from "../loaders/feature-flags"
|
import featureFlagLoader, { featureFlagRouter } from "../loaders/feature-flags"
|
||||||
import {
|
|
||||||
moduleLoader,
|
|
||||||
moduleHelper,
|
|
||||||
registerModules,
|
|
||||||
} from "@medusajs/modules-sdk"
|
|
||||||
import passportLoader from "../loaders/passport"
|
import passportLoader from "../loaders/passport"
|
||||||
import servicesLoader from "../loaders/services"
|
import servicesLoader from "../loaders/services"
|
||||||
import strategiesLoader from "../loaders/strategies"
|
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 { 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 {
|
export interface IBatchJobStrategy extends TransactionBaseService {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 "./batch-job-strategy"
|
||||||
|
export * from "./cart-completion-strategy"
|
||||||
export * from "./file-service"
|
export * from "./file-service"
|
||||||
export * from "./notification-service"
|
|
||||||
export * from "./price-selection-strategy"
|
|
||||||
export * from "./models/base-entity"
|
export * from "./models/base-entity"
|
||||||
export * from "./models/soft-deletable-entity"
|
export * from "./models/soft-deletable-entity"
|
||||||
export * from "./search-service"
|
export * from "./notification-service"
|
||||||
export * from "./payment-service"
|
|
||||||
export * from "./payment-processor"
|
export * from "./payment-processor"
|
||||||
|
export * from "./payment-service"
|
||||||
|
export * from "./price-selection-strategy"
|
||||||
|
export * from "./search-service"
|
||||||
export * from "./services"
|
export * from "./services"
|
||||||
|
export * from "./tax-calculation-strategy"
|
||||||
|
export * from "./tax-service"
|
||||||
|
export * from "./transaction-base-service"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export * from "./cache"
|
export * from "./cache"
|
||||||
export * from "./event-bus"
|
|
||||||
export * from "./stock-location"
|
|
||||||
export * from "./inventory"
|
export * from "./inventory"
|
||||||
|
export * from "./stock-location"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { asValue } from "awilix"
|
import { asValue } from "awilix"
|
||||||
import RealRedis from "ioredis"
|
import Redis from "ioredis"
|
||||||
import FakeRedis from "ioredis-mock"
|
import FakeRedis from "ioredis-mock"
|
||||||
import { ConfigModule, MedusaContainer } from "../types/global"
|
import { EOL } from "os"
|
||||||
import { Logger } from "../types/global"
|
import { ConfigModule, Logger, MedusaContainer } from "../types/global"
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
container: MedusaContainer
|
container: MedusaContainer
|
||||||
@@ -10,19 +10,27 @@ type Options = {
|
|||||||
logger: Logger
|
logger: Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Will be removed when the strict dependency on Redis in the core is removed
|
||||||
async function redisLoader({
|
async function redisLoader({
|
||||||
container,
|
container,
|
||||||
configModule,
|
configModule,
|
||||||
logger,
|
logger,
|
||||||
}: Options): Promise<void> {
|
}: Options): Promise<void> {
|
||||||
if (configModule.projectConfig.redis_url) {
|
if (configModule.projectConfig.redis_url) {
|
||||||
// Economical way of dealing with redis clients
|
const redisClient = new Redis(configModule.projectConfig.redis_url, {
|
||||||
const client = new RealRedis(configModule.projectConfig.redis_url)
|
// Lazy connect to properly handle connection errors
|
||||||
const subscriber = new RealRedis(configModule.projectConfig.redis_url)
|
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({
|
container.register({
|
||||||
redisClient: asValue(client),
|
redisClient: asValue(redisClient),
|
||||||
redisSubscriber: asValue(subscriber),
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "production") {
|
||||||
@@ -38,7 +46,6 @@ async function redisLoader({
|
|||||||
|
|
||||||
container.register({
|
container.register({
|
||||||
redisClient: asValue(client),
|
redisClient: asValue(client),
|
||||||
redisSubscriber: asValue(client),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { MedusaContainer } from "../types/global"
|
|
||||||
import { Logger } from "../types/global"
|
|
||||||
import { EventBusService } from "../services"
|
|
||||||
import { AbstractSearchService } from "../interfaces"
|
import { AbstractSearchService } from "../interfaces"
|
||||||
|
import { EventBusService } from "../services"
|
||||||
|
import { Logger, MedusaContainer } from "../types/global"
|
||||||
|
|
||||||
export const SEARCH_INDEX_EVENT = "SEARCH_INDEX_EVENT"
|
export const SEARCH_INDEX_EVENT = "SEARCH_INDEX_EVENT"
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { asFunction } from "awilix"
|
import { asFunction } from "awilix"
|
||||||
import glob from "glob"
|
import glob from "glob"
|
||||||
|
import { isDefined } from "medusa-core-utils"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { ConfigModule, MedusaContainer } from "../types/global"
|
import { ConfigModule, MedusaContainer } from "../types/global"
|
||||||
import { isDefined } from "medusa-core-utils"
|
|
||||||
import formatRegistrationName from "../utils/format-registration-name"
|
import formatRegistrationName from "../utils/format-registration-name"
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import {
|
|||||||
JoinColumn,
|
JoinColumn,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
OneToOne,
|
OneToOne
|
||||||
} from "typeorm"
|
} from "typeorm"
|
||||||
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
|
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 { Address } from "./address"
|
||||||
import { Cart } from "./cart"
|
import { Cart } from "./cart"
|
||||||
import { Fulfillment } from "./fulfillment"
|
import { Fulfillment } from "./fulfillment"
|
||||||
@@ -18,8 +20,6 @@ import { Order } from "./order"
|
|||||||
import { Payment } from "./payment"
|
import { Payment } from "./payment"
|
||||||
import { Return } from "./return"
|
import { Return } from "./return"
|
||||||
import { ShippingMethod } from "./shipping-method"
|
import { ShippingMethod } from "./shipping-method"
|
||||||
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
|
|
||||||
import { generateEntityId } from "../utils/generate-entity-id"
|
|
||||||
|
|
||||||
export enum SwapFulfillmentStatus {
|
export enum SwapFulfillmentStatus {
|
||||||
NOT_FULFILLED = "not_fulfilled",
|
NOT_FULFILLED = "not_fulfilled",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
|
||||||
import { dataSource } from "../loaders/database"
|
import { dataSource } from "../loaders/database"
|
||||||
import { StagedJob } from "../models"
|
import { StagedJob } from "../models"
|
||||||
import { rowSqlResultsToEntityTransformer } from "../utils/row-sql-results-to-entity-transformer"
|
|
||||||
|
|
||||||
export const StagedJobRepository = dataSource.getRepository(StagedJob).extend({
|
export const StagedJobRepository = dataSource.getRepository(StagedJob).extend({
|
||||||
async insertBulk(jobToCreates: QueryDeepPartialEntity<StagedJob>[]) {
|
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
|
// TODO: remove if statement once this issue is resolved https://github.com/typeorm/typeorm/issues/9850
|
||||||
if (!queryBuilder.connection.driver.isReturningSqlSupported("insert")) {
|
if (!queryBuilder.connection.driver.isReturningSqlSupported("insert")) {
|
||||||
const rawStagedJobs = await queryBuilder.execute()
|
const rawStagedJobs = await queryBuilder.execute()
|
||||||
return rawStagedJobs.generatedMaps
|
return rawStagedJobs.generatedMaps.map((d) =>
|
||||||
|
this.create(d)
|
||||||
|
) as StagedJob[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawStagedJobs = await queryBuilder.returning("*").execute()
|
const rawStagedJobs = await queryBuilder.returning("*").execute()
|
||||||
|
return rawStagedJobs.generatedMaps.map((d) => this.create(d))
|
||||||
return rowSqlResultsToEntityTransformer(
|
|
||||||
rawStagedJobs.raw,
|
|
||||||
queryBuilder,
|
|
||||||
this.queryRunner!
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default StagedJobRepository
|
export default StagedJobRepository
|
||||||
|
|||||||
25
packages/medusa/src/services/__mocks__/staged-job.js
Normal file
25
packages/medusa/src/services/__mocks__/staged-job.js
Normal file
@@ -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 { 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 { BatchJob } from "../../models"
|
||||||
|
import { BatchJobStatus } from "../../types/batch-job"
|
||||||
|
import BatchJobService from "../batch-job"
|
||||||
|
import EventBusService from "../event-bus"
|
||||||
|
|
||||||
const eventBusServiceMock = {
|
const eventBusServiceMock = {
|
||||||
emit: jest.fn(),
|
emit: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
} as unknown as EventBusService
|
} as unknown as EventBusService
|
||||||
const batchJobRepositoryMock = MockRepository({
|
const batchJobRepositoryMock = MockRepository({
|
||||||
create: jest.fn().mockImplementation((data) => {
|
create: jest.fn().mockImplementation((data) => {
|
||||||
return Object.assign(new BatchJob(), data)
|
return Object.assign(new BatchJob(), data)
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('BatchJobService', () => {
|
describe("BatchJobService", () => {
|
||||||
const batchJobId_1 = IdMap.getId("batchJob_1")
|
const batchJobId_1 = IdMap.getId("batchJob_1")
|
||||||
const batchJobService = new BatchJobService({
|
const batchJobService = new BatchJobService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
eventBusService: eventBusServiceMock,
|
eventBusService: eventBusServiceMock,
|
||||||
batchJobRepository: batchJobRepositoryMock
|
batchJobRepository: batchJobRepositoryMock,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('update status', () => {
|
describe("update status", () => {
|
||||||
describe("confirm", () => {
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
dry_run: true,
|
dry_run: true,
|
||||||
status: BatchJobStatus.PRE_PROCESSED
|
status: BatchJobStatus.PRE_PROCESSED,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedBatchJob = await batchJobService.confirm(batchJob)
|
const updatedBatchJob = await batchJobService.confirm(batchJob)
|
||||||
expect(updatedBatchJob.processing_at).not.toBeTruthy()
|
expect(updatedBatchJob.processing_at).not.toBeTruthy()
|
||||||
expect(eventBusServiceMock.emit)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(BatchJobService.Events.CONFIRMED, { id: batchJobId_1 })
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
dry_run: true,
|
dry_run: true,
|
||||||
status: BatchJobStatus.CREATED
|
status: BatchJobStatus.CREATED,
|
||||||
})
|
})
|
||||||
|
|
||||||
const err = await batchJobService.confirm(batchJob)
|
const err = await batchJobService.confirm(batchJob).catch((e) => e)
|
||||||
.catch(e => e)
|
|
||||||
expect(err).toBeTruthy()
|
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)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("complete", () => {
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
dry_run: true,
|
dry_run: true,
|
||||||
status: BatchJobStatus.PROCESSING
|
status: BatchJobStatus.PROCESSING,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedBatchJob = await batchJobService.complete(batchJob)
|
const updatedBatchJob = await batchJobService.complete(batchJob)
|
||||||
expect(updatedBatchJob.completed_at).toBeTruthy()
|
expect(updatedBatchJob.completed_at).toBeTruthy()
|
||||||
expect(eventBusServiceMock.emit)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(BatchJobService.Events.COMPLETED, { id: batchJobId_1 })
|
BatchJobService.Events.COMPLETED,
|
||||||
|
{ id: batchJobId_1 }
|
||||||
|
)
|
||||||
|
|
||||||
const batchJob2 = batchJobRepositoryMock.create({
|
const batchJob2 = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
status: BatchJobStatus.PROCESSING
|
status: BatchJobStatus.PROCESSING,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedBatchJob2 = await batchJobService.complete(batchJob2)
|
const updatedBatchJob2 = await batchJobService.complete(batchJob2)
|
||||||
expect(updatedBatchJob2.completed_at).toBeTruthy()
|
expect(updatedBatchJob2.completed_at).toBeTruthy()
|
||||||
expect(eventBusServiceMock.emit)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(BatchJobService.Events.COMPLETED, { id: batchJobId_1 })
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
dry_run: true,
|
dry_run: true,
|
||||||
status: BatchJobStatus.CREATED
|
status: BatchJobStatus.CREATED,
|
||||||
})
|
})
|
||||||
|
|
||||||
const err = await batchJobService.complete(batchJob)
|
const err = await batchJobService.complete(batchJob).catch((e) => e)
|
||||||
.catch(e => e)
|
|
||||||
expect(err).toBeTruthy()
|
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)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
const batchJob2 = batchJobRepositoryMock.create({
|
const batchJob2 = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
status: BatchJobStatus.PRE_PROCESSED
|
status: BatchJobStatus.PRE_PROCESSED,
|
||||||
})
|
})
|
||||||
|
|
||||||
const err2 = await batchJobService.complete(batchJob2)
|
const err2 = await batchJobService.complete(batchJob2).catch((e) => e)
|
||||||
.catch(e => e)
|
|
||||||
expect(err2).toBeTruthy()
|
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)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("pre processed", () => {
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
dry_run: true,
|
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(updatedBatchJob.pre_processed_at).toBeTruthy()
|
||||||
expect(eventBusServiceMock.emit)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(BatchJobService.Events.PRE_PROCESSED, { id: batchJobId_1 })
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
dry_run: false,
|
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.pre_processed_at).toBeTruthy()
|
||||||
expect(updatedBatchJob.confirmed_at).toBeTruthy()
|
expect(updatedBatchJob.confirmed_at).toBeTruthy()
|
||||||
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(2)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(2)
|
||||||
expect(eventBusServiceMock.emit)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(BatchJobService.Events.PRE_PROCESSED, { id: batchJobId_1 })
|
BatchJobService.Events.PRE_PROCESSED,
|
||||||
expect(eventBusServiceMock.emit)
|
{ id: batchJobId_1 }
|
||||||
.toHaveBeenLastCalledWith(BatchJobService.Events.CONFIRMED, { id: batchJobId_1 })
|
)
|
||||||
|
expect(eventBusServiceMock.emit).toHaveBeenLastCalledWith(
|
||||||
|
BatchJobService.Events.CONFIRMED,
|
||||||
|
{ id: batchJobId_1 }
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("cancel", () => {
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
status: BatchJobStatus.CREATED
|
status: BatchJobStatus.CREATED,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedBatchJob = await batchJobService.cancel(batchJob)
|
const updatedBatchJob = await batchJobService.cancel(batchJob)
|
||||||
expect(updatedBatchJob.canceled_at).toBeTruthy()
|
expect(updatedBatchJob.canceled_at).toBeTruthy()
|
||||||
expect(eventBusServiceMock.emit)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(BatchJobService.Events.CANCELED, { id: batchJobId_1 })
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
status: BatchJobStatus.COMPLETED
|
status: BatchJobStatus.COMPLETED,
|
||||||
})
|
})
|
||||||
|
|
||||||
const err = await batchJobService.cancel(batchJob)
|
const err = await batchJobService.cancel(batchJob).catch((e) => e)
|
||||||
.catch(e => e)
|
|
||||||
expect(err).toBeTruthy()
|
expect(err).toBeTruthy()
|
||||||
expect(err.message).toBe("Cannot cancel completed batch job")
|
expect(err.message).toBe("Cannot cancel completed batch job")
|
||||||
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
@@ -170,30 +190,35 @@ describe('BatchJobService', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("processing", () => {
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
status: BatchJobStatus.CONFIRMED
|
status: BatchJobStatus.CONFIRMED,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedBatchJob = await batchJobService.setProcessing(batchJob)
|
const updatedBatchJob = await batchJobService.setProcessing(batchJob)
|
||||||
expect(updatedBatchJob.processing_at).toBeTruthy()
|
expect(updatedBatchJob.processing_at).toBeTruthy()
|
||||||
expect(eventBusServiceMock.emit)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(BatchJobService.Events.PROCESSING, { id: batchJobId_1 })
|
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({
|
const batchJob = batchJobRepositoryMock.create({
|
||||||
id: batchJobId_1,
|
id: batchJobId_1,
|
||||||
status: BatchJobStatus.COMPLETED
|
status: BatchJobStatus.COMPLETED,
|
||||||
})
|
})
|
||||||
|
|
||||||
const err = await batchJobService.setProcessing(batchJob)
|
const err = await batchJobService
|
||||||
.catch(e => e)
|
.setProcessing(batchJob)
|
||||||
|
.catch((e) => e)
|
||||||
expect(err).toBeTruthy()
|
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)
|
expect(eventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,36 +1,35 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
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 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 currencyCode = IdMap.getId("currency-1")
|
||||||
const eventBusServiceMock = {
|
const eventBusServiceMock = {
|
||||||
emit: jest.fn(),
|
emit: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
} as unknown as EventBusService
|
} as unknown as EventBusService
|
||||||
const currencyRepositoryMock = MockRepository({
|
const currencyRepositoryMock = MockRepository({
|
||||||
findOne: jest.fn().mockImplementation(() => {
|
findOne: jest.fn().mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
code: currencyCode
|
code: currencyCode,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
save: jest.fn().mockImplementation((data) => {
|
save: jest.fn().mockImplementation((data) => {
|
||||||
return Object.assign(new Currency(), data)
|
return Object.assign(new Currency(), data)
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("CurrencyService", () => {
|
||||||
describe('CurrencyService', () => {
|
|
||||||
const currencyService = new CurrencyService({
|
const currencyService = new CurrencyService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
currencyRepository: currencyRepositoryMock,
|
currencyRepository: currencyRepositoryMock,
|
||||||
eventBusService: eventBusServiceMock,
|
eventBusService: eventBusServiceMock,
|
||||||
featureFlagRouter: new FlagRouter({
|
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 { Queue } from "bullmq"
|
||||||
import config from "../../loaders/config"
|
|
||||||
import JobSchedulerService from "../job-scheduler"
|
import JobSchedulerService from "../job-scheduler"
|
||||||
|
|
||||||
jest.genMockFromModule("bull")
|
jest.genMockFromModule("bullmq")
|
||||||
jest.mock("bull")
|
jest.mock("bullmq")
|
||||||
jest.mock("../../loaders/config")
|
jest.mock("ioredis")
|
||||||
|
|
||||||
config.redisURI = "testhost"
|
|
||||||
|
|
||||||
const loggerMock = {
|
const loggerMock = {
|
||||||
info: jest.fn().mockReturnValue(console.log),
|
info: jest.fn().mockReturnValue(console.log),
|
||||||
@@ -15,90 +12,112 @@ const loggerMock = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("JobSchedulerService", () => {
|
describe("JobSchedulerService", () => {
|
||||||
describe("constructor", () => {
|
let scheduler
|
||||||
let jobScheduler
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.resetAllMocks()
|
|
||||||
|
|
||||||
jobScheduler = new JobSchedulerService({
|
describe("constructor", () => {
|
||||||
logger: loggerMock,
|
beforeAll(() => {
|
||||||
})
|
jest.clearAllMocks()
|
||||||
|
|
||||||
|
scheduler = new JobSchedulerService(
|
||||||
|
{
|
||||||
|
logger: loggerMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
projectConfig: {
|
||||||
|
redis_url: "testhost",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates bull queue", () => {
|
it("creates bull queue", () => {
|
||||||
expect(Bull).toHaveBeenCalledTimes(1)
|
expect(Queue).toHaveBeenCalledTimes(1)
|
||||||
expect(Bull).toHaveBeenCalledWith("scheduled-jobs:queue", {
|
expect(Queue).toHaveBeenCalledWith("scheduled-jobs:queue", {
|
||||||
createClient: expect.any(Function),
|
connection: expect.any(Object),
|
||||||
|
prefix: "JobSchedulerService",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
let jobScheduler
|
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,
|
logger: loggerMock,
|
||||||
})
|
},
|
||||||
|
{
|
||||||
jobScheduler.create(
|
projectConfig: {
|
||||||
"eventName",
|
redis_url: "testhost",
|
||||||
{ 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" },
|
|
||||||
},
|
},
|
||||||
{
|
}
|
||||||
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", () => {
|
it("calls logger", () => {
|
||||||
let jobScheduler
|
expect(loggerMock.info).toHaveBeenCalled()
|
||||||
let result
|
expect(loggerMock.info).toHaveBeenCalledWith(
|
||||||
describe("successfully runs the worker", () => {
|
"Processing scheduled job: eventName"
|
||||||
beforeAll(async () => {
|
)
|
||||||
jest.resetAllMocks()
|
})
|
||||||
|
|
||||||
jobScheduler = new JobSchedulerService(
|
it("returns array with hi", async () => {
|
||||||
{
|
expect(result).toEqual(["hi"])
|
||||||
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"])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
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 { 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"
|
import { LineItemServiceMock } from "../__mocks__/line-item"
|
||||||
|
|
||||||
const taxProviderServiceMock = {
|
const taxProviderServiceMock = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import { OrderEditItemChangeType, OrderEditStatus } from "../../models"
|
import { OrderEditItemChangeType, OrderEditStatus } from "../../models"
|
||||||
|
import EventBusService from "../event-bus"
|
||||||
import {
|
import {
|
||||||
EventBusService,
|
|
||||||
LineItemService,
|
LineItemService,
|
||||||
NewTotalsService,
|
NewTotalsService,
|
||||||
OrderEditItemChangeService,
|
OrderEditItemChangeService,
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import { CustomerService, EventBusService, PaymentCollectionService, PaymentProviderService, } from "../index"
|
import {
|
||||||
import { PaymentCollection, PaymentCollectionStatus, PaymentCollectionType, } from "../../models"
|
PaymentCollection,
|
||||||
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
PaymentCollectionStatus,
|
||||||
import { DefaultProviderMock, PaymentProviderServiceMock, } from "../__mocks__/payment-provider"
|
PaymentCollectionType
|
||||||
import { CustomerServiceMock } from "../__mocks__/customer"
|
} from "../../models"
|
||||||
import { PaymentCollectionsSessionsBatchInput } from "../../types/payment-collection"
|
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", () => {
|
describe("PaymentCollectionService", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -414,9 +426,7 @@ describe("PaymentCollectionService", () => {
|
|||||||
"lebron"
|
"lebron"
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
|
||||||
1
|
|
||||||
)
|
|
||||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
@@ -430,12 +440,8 @@ describe("PaymentCollectionService", () => {
|
|||||||
"lebron"
|
"lebron"
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(0)
|
||||||
0
|
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(1)
|
||||||
)
|
|
||||||
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
)
|
|
||||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
@@ -453,15 +459,9 @@ describe("PaymentCollectionService", () => {
|
|||||||
IdMap.getId("lebron")
|
IdMap.getId("lebron")
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
|
||||||
1
|
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(0)
|
||||||
)
|
expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes(1)
|
||||||
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(
|
|
||||||
0
|
|
||||||
)
|
|
||||||
expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
@@ -508,9 +508,7 @@ describe("PaymentCollectionService", () => {
|
|||||||
"customer1"
|
"customer1"
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(0)
|
||||||
0
|
|
||||||
)
|
|
||||||
expect(ret).rejects.toThrow(
|
expect(ret).rejects.toThrow(
|
||||||
`The sum of sessions is not equal to 100 on Payment Collection`
|
`The sum of sessions is not equal to 100 on Payment Collection`
|
||||||
)
|
)
|
||||||
@@ -531,9 +529,7 @@ describe("PaymentCollectionService", () => {
|
|||||||
"customer1"
|
"customer1"
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(0)
|
||||||
0
|
|
||||||
)
|
|
||||||
expect(multiRet).rejects.toThrow(
|
expect(multiRet).rejects.toThrow(
|
||||||
`The sum of sessions is not equal to 100 on Payment Collection`
|
`The sum of sessions is not equal to 100 on Payment Collection`
|
||||||
)
|
)
|
||||||
@@ -580,12 +576,8 @@ describe("PaymentCollectionService", () => {
|
|||||||
"lebron"
|
"lebron"
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
|
||||||
1
|
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(1)
|
||||||
)
|
|
||||||
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
)
|
|
||||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
@@ -603,15 +595,9 @@ describe("PaymentCollectionService", () => {
|
|||||||
IdMap.getId("lebron")
|
IdMap.getId("lebron")
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
|
||||||
1
|
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(0)
|
||||||
)
|
expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes(1)
|
||||||
expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes(
|
|
||||||
0
|
|
||||||
)
|
|
||||||
expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
@@ -623,13 +609,9 @@ describe("PaymentCollectionService", () => {
|
|||||||
"customer1"
|
"customer1"
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(PaymentProviderServiceMock.refreshSession).toHaveBeenCalledTimes(1)
|
||||||
PaymentProviderServiceMock.refreshSession
|
|
||||||
).toHaveBeenCalledTimes(1)
|
|
||||||
expect(DefaultProviderMock.deletePayment).toHaveBeenCalledTimes(1)
|
expect(DefaultProviderMock.deletePayment).toHaveBeenCalledTimes(1)
|
||||||
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes(1)
|
||||||
1
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should throw to refresh a payment session that doesn't exist", async () => {
|
it("should throw to refresh a payment session that doesn't exist", async () => {
|
||||||
@@ -689,9 +671,7 @@ describe("PaymentCollectionService", () => {
|
|||||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(2)
|
||||||
2
|
|
||||||
)
|
|
||||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -704,9 +684,7 @@ describe("PaymentCollectionService", () => {
|
|||||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(1)
|
||||||
1
|
|
||||||
)
|
|
||||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -719,9 +697,7 @@ describe("PaymentCollectionService", () => {
|
|||||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(
|
expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes(0)
|
||||||
0
|
|
||||||
)
|
|
||||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import { IdMap, MockManager as manager } from "medusa-test-utils"
|
import { IdMap, MockManager as manager } from "medusa-test-utils"
|
||||||
import ProductCategoryService from "../product-category"
|
import { EventBusService, ProductCategoryService } from "../"
|
||||||
import { EventBusService } from "../"
|
|
||||||
import {
|
import {
|
||||||
invalidProdCategoryId,
|
invalidProdCategoryId,
|
||||||
productCategoryRepositoryMock as productCategoryRepository,
|
productCategoryRepositoryMock as productCategoryRepository,
|
||||||
validProdCategoryId,
|
validProdCategoryId,
|
||||||
validProdCategoryIdWithChildren,
|
validProdCategoryIdWithChildren, validProdCategoryRankChange, validProdCategoryWithSiblings
|
||||||
validProdCategoryWithSiblings,
|
|
||||||
validProdCategoryRankChange
|
|
||||||
} from "../../repositories/__mocks__/product-category"
|
} from "../../repositories/__mocks__/product-category"
|
||||||
import { tempReorderRank } from "../../types/product-category"
|
import { tempReorderRank } from "../../types/product-category"
|
||||||
import { EventBusServiceMock as eventBusService } from "../__mocks__/event-bus"
|
import { EventBusServiceMock as eventBusService } from "../__mocks__/event-bus"
|
||||||
@@ -74,7 +71,9 @@ describe("ProductCategoryService", () => {
|
|||||||
const [result, count] = await productCategoryService
|
const [result, count] = await productCategoryService
|
||||||
.listAndCount({ q: IdMap.getId(invalidProdCategoryId) })
|
.listAndCount({ q: IdMap.getId(invalidProdCategoryId) })
|
||||||
|
|
||||||
expect(productCategoryRepository.getFreeTextSearchResultsAndCount).toHaveBeenCalledTimes(1)
|
expect(
|
||||||
|
productCategoryRepository.getFreeTextSearchResultsAndCount
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
expect(count).toEqual(0)
|
expect(count).toEqual(0)
|
||||||
})
|
})
|
||||||
@@ -115,8 +114,9 @@ describe("ProductCategoryService", () => {
|
|||||||
|
|
||||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||||
expect(eventBusService.emit).toHaveBeenCalledWith(
|
expect(eventBusService.emit).toHaveBeenCalledWith(
|
||||||
"product-category.created", {
|
"product-category.created",
|
||||||
"id": IdMap.getId(validProdCategoryId)
|
{
|
||||||
|
id: IdMap.getId(validProdCategoryId),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -129,7 +129,9 @@ describe("ProductCategoryService", () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(productCategoryRepository.delete).toBeCalledTimes(1)
|
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 () => {
|
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).toHaveBeenCalledTimes(1)
|
||||||
expect(eventBusService.emit).toHaveBeenCalledWith(
|
expect(eventBusService.emit).toHaveBeenCalledWith(
|
||||||
"product-category.deleted", {
|
"product-category.deleted",
|
||||||
"id": IdMap.getId(validProdCategoryId)
|
{
|
||||||
|
id: IdMap.getId(validProdCategoryId),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -182,11 +185,9 @@ describe("ProductCategoryService", () => {
|
|||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
it("successfully updates a product category", async () => {
|
it("successfully updates a product category", async () => {
|
||||||
await productCategoryService.update(
|
await productCategoryService.update(IdMap.getId(validProdCategoryId), {
|
||||||
IdMap.getId(validProdCategoryId), {
|
name: "bathrobes",
|
||||||
name: "bathrobes",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(productCategoryRepository.save).toHaveBeenCalledTimes(1)
|
expect(productCategoryRepository.save).toHaveBeenCalledTimes(1)
|
||||||
expect(productCategoryRepository.save).toHaveBeenCalledWith(
|
expect(productCategoryRepository.save).toHaveBeenCalledWith(
|
||||||
@@ -221,28 +222,32 @@ describe("ProductCategoryService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("fails on not-found Id product category", async () => {
|
it("fails on not-found Id product category", async () => {
|
||||||
const error = await productCategoryService.update(
|
const error = await productCategoryService
|
||||||
IdMap.getId(invalidProdCategoryId), {
|
.update(IdMap.getId(invalidProdCategoryId), {
|
||||||
name: "bathrobes",
|
name: "bathrobes",
|
||||||
}
|
})
|
||||||
).catch(e => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(error.message).toBe(
|
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 () => {
|
it("emits a message on successful update", async () => {
|
||||||
const result = await productCategoryService.update(
|
const result = await productCategoryService.update(
|
||||||
IdMap.getId(validProdCategoryId), {
|
IdMap.getId(validProdCategoryId),
|
||||||
|
{
|
||||||
name: "bathrobes",
|
name: "bathrobes",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||||
expect(eventBusService.emit).toHaveBeenCalledWith(
|
expect(eventBusService.emit).toHaveBeenCalledWith(
|
||||||
"product-category.updated", {
|
"product-category.updated",
|
||||||
"id": IdMap.getId(validProdCategoryId)
|
{
|
||||||
|
id: IdMap.getId(validProdCategoryId),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
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 PublishableApiKeyService from "../publishable-api-key"
|
||||||
|
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||||
|
|
||||||
const pubKeyToRetrieve = {
|
const pubKeyToRetrieve = {
|
||||||
id: IdMap.getId("pub-key-to-retrieve"),
|
id: IdMap.getId("pub-key-to-retrieve"),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import { CreateRegionInput } from "../../types/region"
|
import { CreateRegionInput } from "../../types/region"
|
||||||
import { FlagRouter } from "../../utils/flag-router"
|
import { FlagRouter } from "../../utils/flag-router"
|
||||||
|
import EventBusService from "../event-bus"
|
||||||
import {
|
import {
|
||||||
EventBusService,
|
|
||||||
FulfillmentProviderService,
|
FulfillmentProviderService,
|
||||||
PaymentProviderService,
|
PaymentProviderService,
|
||||||
StoreService,
|
StoreService,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
|
import { EventBusService, StoreService } from "../index"
|
||||||
import SalesChannelService from "../sales-channel"
|
import SalesChannelService from "../sales-channel"
|
||||||
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
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"
|
import { store, StoreServiceMock } from "../__mocks__/store"
|
||||||
|
|
||||||
describe("SalesChannelService", () => {
|
describe("SalesChannelService", () => {
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import SwapService from "../swap"
|
import { Order, Swap } from "../../models"
|
||||||
import {
|
import { SwapRepository } from "../../repositories/swap"
|
||||||
ProductVariantInventoryServiceMock
|
import CartService from "../cart"
|
||||||
} from "../__mocks__/product-variant-inventory"
|
import EventBusService from "../event-bus"
|
||||||
import {
|
|
||||||
LineItemAdjustmentServiceMock
|
|
||||||
} from "../__mocks__/line-item-adjustment"
|
|
||||||
import {
|
import {
|
||||||
CustomShippingOptionService,
|
CustomShippingOptionService,
|
||||||
EventBusService,
|
|
||||||
FulfillmentService,
|
FulfillmentService,
|
||||||
LineItemService,
|
LineItemService,
|
||||||
OrderService,
|
OrderService,
|
||||||
@@ -16,12 +12,16 @@ import {
|
|||||||
ProductVariantInventoryService,
|
ProductVariantInventoryService,
|
||||||
ReturnService,
|
ReturnService,
|
||||||
ShippingOptionService,
|
ShippingOptionService,
|
||||||
TotalsService,
|
TotalsService
|
||||||
} from "../index"
|
} from "../index"
|
||||||
import CartService from "../cart"
|
|
||||||
import { Order, Swap } from "../../models"
|
|
||||||
import { SwapRepository } from "../../repositories/swap"
|
|
||||||
import LineItemAdjustmentService from "../line-item-adjustment"
|
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 ******************** */
|
/* ******************** DEFAULT REPOSITORY MOCKS ******************** */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import { Request } from "express"
|
||||||
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import { BatchJob } from "../models"
|
import { BatchJob } from "../models"
|
||||||
import { BatchJobRepository } from "../repositories/batch-job"
|
import { BatchJobRepository } from "../repositories/batch-job"
|
||||||
import {
|
import {
|
||||||
@@ -10,11 +13,9 @@ import {
|
|||||||
FilterableBatchJobProps,
|
FilterableBatchJobProps,
|
||||||
} from "../types/batch-job"
|
} from "../types/batch-job"
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig } from "../types/common"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
|
||||||
import { buildQuery } from "../utils"
|
import { buildQuery } from "../utils"
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import EventBusService from "./event-bus"
|
||||||
import { EventBusService, StrategyResolverService } from "./index"
|
import { StrategyResolverService } from "./index"
|
||||||
import { Request } from "express"
|
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { EventBusService } from "."
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import {
|
import {
|
||||||
DiscountCondition,
|
DiscountCondition,
|
||||||
DiscountConditionCustomerGroup,
|
DiscountConditionCustomerGroup,
|
||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
import { DiscountConditionRepository } from "../repositories/discount-condition"
|
import { DiscountConditionRepository } from "../repositories/discount-condition"
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig } from "../types/common"
|
||||||
import { DiscountConditionInput } from "../types/discount"
|
import { DiscountConditionInput } from "../types/discount"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
|
||||||
import { buildQuery, PostgresError } from "../utils"
|
import { buildQuery, PostgresError } from "../utils"
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
In,
|
In,
|
||||||
} from "typeorm"
|
} from "typeorm"
|
||||||
import {
|
import {
|
||||||
EventBusService,
|
|
||||||
NewTotalsService,
|
NewTotalsService,
|
||||||
ProductService,
|
ProductService,
|
||||||
RegionService,
|
RegionService,
|
||||||
@@ -36,12 +35,13 @@ import {
|
|||||||
UpdateDiscountInput,
|
UpdateDiscountInput,
|
||||||
UpdateDiscountRuleInput,
|
UpdateDiscountRuleInput,
|
||||||
} from "../types/discount"
|
} from "../types/discount"
|
||||||
|
import { CalculationContextData } from "../types/totals"
|
||||||
import { buildQuery, setMetadata } from "../utils"
|
import { buildQuery, setMetadata } from "../utils"
|
||||||
import { isFuture, isPast } from "../utils/date-helpers"
|
import { isFuture, isPast } from "../utils/date-helpers"
|
||||||
import { FlagRouter } from "../utils/flag-router"
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
import CustomerService from "./customer"
|
import CustomerService from "./customer"
|
||||||
import DiscountConditionService from "./discount-condition"
|
import DiscountConditionService from "./discount-condition"
|
||||||
import { CalculationContextData } from "../types/totals"
|
import EventBusService from "./event-bus"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides layer to manipulate discounts.
|
* Provides layer to manipulate discounts.
|
||||||
|
|||||||
@@ -1,150 +1,68 @@
|
|||||||
import Bull, { JobOptions } from "bull"
|
import { EventBusTypes } from "@medusajs/types"
|
||||||
import Redis from "ioredis"
|
import { EventBusUtils } from "@medusajs/utils"
|
||||||
import { DeepPartial, EntityManager, In } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import { ulid } from "ulid"
|
|
||||||
import { StagedJob } from "../models"
|
import { StagedJob } from "../models"
|
||||||
import { StagedJobRepository } from "../repositories/staged-job"
|
import { ConfigModule } from "../types/global"
|
||||||
import { ConfigModule, Logger } from "../types/global"
|
|
||||||
import { isString } from "../utils"
|
import { isString } from "../utils"
|
||||||
import { sleep } from "../utils/sleep"
|
import { sleep } from "../utils/sleep"
|
||||||
import JobSchedulerService, { CreateJobOptions } from "./job-scheduler"
|
import StagedJobService from "./staged-job"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
stagedJobService: StagedJobService
|
||||||
logger: Logger
|
eventBusModuleService: EventBusUtils.AbstractEventBusModuleService
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can keep track of multiple subscribers to different events and run the
|
* Can keep track of multiple subscribers to different events and run the
|
||||||
* subscribers when events happen. Events will run asynchronously.
|
* subscribers when events happen. Events will run asynchronously.
|
||||||
*/
|
*/
|
||||||
export default class EventBusService {
|
export default class EventBusService
|
||||||
|
extends TransactionBaseService
|
||||||
|
implements EventBusTypes.IEventBusService
|
||||||
|
{
|
||||||
protected readonly config_: ConfigModule
|
protected readonly config_: ConfigModule
|
||||||
protected readonly manager_: EntityManager
|
protected readonly stagedJobService_: StagedJobService
|
||||||
protected readonly logger_: Logger
|
// eslint-disable-next-line max-len
|
||||||
protected readonly stagedJobRepository_: typeof StagedJobRepository
|
protected readonly eventBusModuleService_: EventBusUtils.AbstractEventBusModuleService
|
||||||
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 shouldEnqueuerRun: boolean
|
protected shouldEnqueuerRun: boolean
|
||||||
protected transactionManager_: EntityManager | undefined
|
|
||||||
protected enqueue_: Promise<void>
|
protected enqueue_: Promise<void>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{ stagedJobService, eventBusModuleService }: InjectedDependencies,
|
||||||
manager,
|
config,
|
||||||
logger,
|
isSingleton = true
|
||||||
stagedJobRepository,
|
|
||||||
redisClient,
|
|
||||||
redisSubscriber,
|
|
||||||
jobSchedulerService,
|
|
||||||
}: InjectedDependencies,
|
|
||||||
config: ConfigModule,
|
|
||||||
singleton = true
|
|
||||||
) {
|
) {
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
super(arguments[0])
|
||||||
|
|
||||||
this.config_ = config
|
this.config_ = config
|
||||||
this.manager_ = manager
|
this.eventBusModuleService_ = eventBusModuleService
|
||||||
this.logger_ = logger
|
this.stagedJobService_ = stagedJobService
|
||||||
this.jobSchedulerService_ = jobSchedulerService
|
|
||||||
this.stagedJobRepository_ = stagedJobRepository
|
|
||||||
|
|
||||||
if (singleton) {
|
if (process.env.NODE_ENV !== "test" && isSingleton) {
|
||||||
const opts = {
|
this.startEnqueuer()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
withTransaction(transactionManager): this | EventBusService {
|
withTransaction(transactionManager?: EntityManager): this {
|
||||||
if (!transactionManager) {
|
if (!transactionManager) {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
const cloned = new EventBusService(
|
const cloned = new (this.constructor as any)(
|
||||||
{
|
{
|
||||||
manager: transactionManager,
|
manager: transactionManager,
|
||||||
stagedJobRepository: this.stagedJobRepository_,
|
stagedJobService: this.stagedJobService_,
|
||||||
jobSchedulerService: this.jobSchedulerService_,
|
eventBusModuleService: this.eventBusModuleService_,
|
||||||
logger: this.logger_,
|
|
||||||
redisClient: this.redisClient_,
|
|
||||||
redisSubscriber: this.redisSubscriber_,
|
|
||||||
},
|
},
|
||||||
this.config_,
|
this.config_,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cloned.manager_ = transactionManager
|
||||||
cloned.transactionManager_ = transactionManager
|
cloned.transactionManager_ = transactionManager
|
||||||
cloned.queue_ = this.queue_
|
|
||||||
|
|
||||||
return cloned
|
return cloned
|
||||||
}
|
}
|
||||||
@@ -153,70 +71,36 @@ export default class EventBusService {
|
|||||||
* Adds a function to a list of event subscribers.
|
* Adds a function to a list of event subscribers.
|
||||||
* @param event - the event that the subscriber will listen for.
|
* @param event - the event that the subscriber will listen for.
|
||||||
* @param subscriber - the function to be called when a certain event
|
* @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.
|
* happens. Subscribers must return a Promise.
|
||||||
|
* @param context - subscriber context
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
subscribe(
|
subscribe(
|
||||||
event: string | symbol,
|
event: string | symbol,
|
||||||
subscriber: Subscriber,
|
subscriber: EventBusTypes.Subscriber,
|
||||||
context?: SubscriberContext
|
context?: EventBusTypes.SubscriberContext
|
||||||
): this {
|
): this {
|
||||||
if (typeof subscriber !== "function") {
|
if (typeof subscriber !== "function") {
|
||||||
throw new Error("Subscriber must be a function")
|
throw new Error("Subscriber must be a function")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
this.eventBusModuleService_.subscribe(event, subscriber, context)
|
||||||
* 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,
|
|
||||||
])
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a function to a list of event subscribers.
|
* Removes function from the list of event subscribers.
|
||||||
* @param event - the event that the subscriber will listen for.
|
* @param event - the event of the subcriber.
|
||||||
* @param subscriber - the function to be called when a certain event
|
* @param subscriber - the function to be removed
|
||||||
* happens. Subscribers must return a Promise.
|
* @param context - subscriber context
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
unsubscribe(event: string | symbol, subscriber: Subscriber): this {
|
unsubscribe(
|
||||||
if (typeof subscriber !== "function") {
|
event: string | symbol,
|
||||||
throw new Error("Subscriber must be a function")
|
subscriber: EventBusTypes.Subscriber,
|
||||||
}
|
context: EventBusTypes.SubscriberContext
|
||||||
|
): this {
|
||||||
const existingSubscribers = this.eventToSubscribersMap_.get(event)
|
this.eventBusModuleService_.unsubscribe(event, subscriber, context)
|
||||||
|
|
||||||
if (existingSubscribers?.length) {
|
|
||||||
const subIndex = existingSubscribers?.findIndex(
|
|
||||||
(sub) => sub.subscriber === subscriber
|
|
||||||
)
|
|
||||||
|
|
||||||
if (subIndex !== -1) {
|
|
||||||
this.eventToSubscribersMap_.get(event)?.splice(subIndex as number, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +109,7 @@ export default class EventBusService {
|
|||||||
* @param data - The data to use to process the events
|
* @param data - The data to use to process the events
|
||||||
* @return the jobs from our queue
|
* @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.
|
* Calls all subscribers when an event occurs.
|
||||||
@@ -237,80 +121,54 @@ export default class EventBusService {
|
|||||||
async emit<T>(
|
async emit<T>(
|
||||||
eventName: string,
|
eventName: string,
|
||||||
data: T,
|
data: T,
|
||||||
options?: Record<string, unknown> & EmitOptions
|
options?: Record<string, unknown>
|
||||||
): Promise<StagedJob | void>
|
): Promise<StagedJob | void>
|
||||||
|
|
||||||
async emit<
|
async emit<
|
||||||
T,
|
T,
|
||||||
TInput extends string | EmitData<T>[] = string,
|
TInput extends string | EventBusTypes.EmitData<T>[] = string,
|
||||||
TResult = TInput extends EmitData<T>[] ? StagedJob[] : StagedJob
|
TResult = TInput extends EventBusTypes.EmitData<T>[]
|
||||||
|
? StagedJob[]
|
||||||
|
: StagedJob
|
||||||
>(
|
>(
|
||||||
eventNameOrData: TInput,
|
eventNameOrData: TInput,
|
||||||
data?: T,
|
data?: T,
|
||||||
options: Record<string, unknown> & EmitOptions = {}
|
options: Record<string, unknown> = {}
|
||||||
): Promise<TResult | void> {
|
): Promise<TResult | void> {
|
||||||
const globalEventOptions = this.config_?.projectConfig?.event_options ?? {}
|
const manager = this.activeManager_
|
||||||
|
|
||||||
const isBulkEmit = !isString(eventNameOrData)
|
const isBulkEmit = !isString(eventNameOrData)
|
||||||
const events = isBulkEmit
|
const events = isBulkEmit
|
||||||
? eventNameOrData.map((event) => ({
|
? eventNameOrData.map((event) => ({
|
||||||
data: { eventName: event.eventName, data: event.data },
|
eventName: event.eventName,
|
||||||
opts: event.opts,
|
data: event.data,
|
||||||
|
options: event.options,
|
||||||
}))
|
}))
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
data: { eventName: eventNameOrData, data },
|
eventName: eventNameOrData,
|
||||||
opts: options,
|
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
|
* We store events in the database when in an ongoing transaction.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* 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.
|
* as part of the rollback.
|
||||||
*/
|
*/
|
||||||
if (this.transactionManager_) {
|
const stagedJobs = await this.stagedJobService_
|
||||||
const stagedJobRepository = this.transactionManager_.withRepository(
|
.withTransaction(manager)
|
||||||
this.stagedJobRepository_
|
.create(events)
|
||||||
)
|
|
||||||
|
|
||||||
const jobsToCreate = events.map((event) => {
|
return (!isBulkEmit ? stagedJobs[0] : stagedJobs) as unknown as TResult
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startEnqueuer(): void {
|
startEnqueuer(): void {
|
||||||
@@ -324,17 +182,14 @@ export default class EventBusService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async enqueuer_(): Promise<void> {
|
async enqueuer_(): Promise<void> {
|
||||||
while (this.shouldEnqueuerRun) {
|
const listConfig = {
|
||||||
const listConfig = {
|
relations: [],
|
||||||
relations: [],
|
skip: 0,
|
||||||
skip: 0,
|
take: 1000,
|
||||||
take: 1000,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const stagedJobRepo = this.manager_.withRepository(
|
while (this.shouldEnqueuerRun) {
|
||||||
this.stagedJobRepository_
|
const jobs = await this.stagedJobService_.list(listConfig)
|
||||||
)
|
|
||||||
const jobs = await stagedJobRepo.find(listConfig)
|
|
||||||
|
|
||||||
if (!jobs.length) {
|
if (!jobs.length) {
|
||||||
await sleep(3000)
|
await sleep(3000)
|
||||||
@@ -343,138 +198,17 @@ export default class EventBusService {
|
|||||||
|
|
||||||
const eventsData = jobs.map((job) => {
|
const eventsData = jobs.map((job) => {
|
||||||
return {
|
return {
|
||||||
data: { eventName: job.event_name, data: job.data },
|
eventName: job.event_name,
|
||||||
opts: { jobId: job.id, ...job.options },
|
data: job.data,
|
||||||
|
options: { jobId: job.id, ...job.options },
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.queue_.addBulk(eventsData).then(async () => {
|
await this.eventBusModuleService_.emit(eventsData).then(async () => {
|
||||||
return await stagedJobRepo.delete({ id: In(jobs.map((j) => j.id)) })
|
return await this.stagedJobService_.delete(jobs.map((j) => j.id))
|
||||||
})
|
})
|
||||||
|
|
||||||
await sleep(3000)
|
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,7 +1,6 @@
|
|||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import randomize from "randomatic"
|
import randomize from "randomatic"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { EventBusService } from "."
|
|
||||||
import { TransactionBaseService } from "../interfaces"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import { GiftCard, Region } from "../models"
|
import { GiftCard, Region } from "../models"
|
||||||
import { GiftCardRepository } from "../repositories/gift-card"
|
import { GiftCardRepository } from "../repositories/gift-card"
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
UpdateGiftCardInput,
|
UpdateGiftCardInput,
|
||||||
} from "../types/gift-card"
|
} from "../types/gift-card"
|
||||||
import { buildQuery, setMetadata } from "../utils"
|
import { buildQuery, setMetadata } from "../utils"
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
import RegionService from "./region"
|
import RegionService from "./region"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
|
|||||||
@@ -19,33 +19,34 @@ export { default as IdempotencyKeyService } from "./idempotency-key"
|
|||||||
export { default as LineItemService } from "./line-item"
|
export { default as LineItemService } from "./line-item"
|
||||||
export { default as LineItemAdjustmentService } from "./line-item-adjustment"
|
export { default as LineItemAdjustmentService } from "./line-item-adjustment"
|
||||||
export { default as MiddlewareService } from "./middleware"
|
export { default as MiddlewareService } from "./middleware"
|
||||||
|
export { default as NewTotalsService } from "./new-totals"
|
||||||
export { default as NoteService } from "./note"
|
export { default as NoteService } from "./note"
|
||||||
export { default as NotificationService } from "./notification"
|
export { default as NotificationService } from "./notification"
|
||||||
export { default as OauthService } from "./oauth"
|
export { default as OauthService } from "./oauth"
|
||||||
export { default as OrderService } from "./order"
|
export { default as OrderService } from "./order"
|
||||||
export { default as OrderEditService } from "./order-edit"
|
export { default as OrderEditService } from "./order-edit"
|
||||||
export { default as OrderEditItemChangeService } from "./order-edit-item-change"
|
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 PaymentCollectionService } from "./payment-collection"
|
||||||
export { default as PaymentProviderService } from "./payment-provider"
|
export { default as PaymentProviderService } from "./payment-provider"
|
||||||
export { default as PaymentService } from "./payment"
|
|
||||||
export { default as PriceListService } from "./price-list"
|
export { default as PriceListService } from "./price-list"
|
||||||
export { default as PricingService } from "./pricing"
|
export { default as PricingService } from "./pricing"
|
||||||
export { default as ProductService } from "./product"
|
export { default as ProductService } from "./product"
|
||||||
export { default as ProductCategoryService } from "./product-category"
|
export { default as ProductCategoryService } from "./product-category"
|
||||||
export { default as ProductCollectionService } from "./product-collection"
|
export { default as ProductCollectionService } from "./product-collection"
|
||||||
export { default as ProductTypeService } from "./product-type"
|
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 ProductVariantService } from "./product-variant"
|
||||||
|
export { default as ProductVariantInventoryService } from "./product-variant-inventory"
|
||||||
export { default as RegionService } from "./region"
|
export { default as RegionService } from "./region"
|
||||||
export { default as ReturnService } from "./return"
|
export { default as ReturnService } from "./return"
|
||||||
export { default as ReturnReasonService } from "./return-reason"
|
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 SalesChannelInventoryService } from "./sales-channel-inventory"
|
||||||
export { default as SalesChannelLocationService } from "./sales-channel-location"
|
export { default as SalesChannelLocationService } from "./sales-channel-location"
|
||||||
export { default as SalesChannelService } from "./sales-channel"
|
|
||||||
export { default as SearchService } from "./search"
|
export { default as SearchService } from "./search"
|
||||||
export { default as ShippingOptionService } from "./shipping-option"
|
export { default as ShippingOptionService } from "./shipping-option"
|
||||||
export { default as ShippingProfileService } from "./shipping-profile"
|
export { default as ShippingProfileService } from "./shipping-profile"
|
||||||
|
export { default as StagedJobService } from "./staged-job"
|
||||||
export { default as StoreService } from "./store"
|
export { default as StoreService } from "./store"
|
||||||
export { default as StrategyResolverService } from "./strategy-resolver"
|
export { default as StrategyResolverService } from "./strategy-resolver"
|
||||||
export { default as SwapService } from "./swap"
|
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 TaxRateService } from "./tax-rate"
|
||||||
export { default as TokenService } from "./token"
|
export { default as TokenService } from "./token"
|
||||||
export { default as TotalsService } from "./totals"
|
export { default as TotalsService } from "./totals"
|
||||||
export { default as NewTotalsService } from "./new-totals"
|
|
||||||
export { default as UserService } from "./user"
|
export { default as UserService } from "./user"
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import jwt, { JwtPayload } from "jsonwebtoken"
|
import jwt, { JwtPayload } from "jsonwebtoken"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { EventBusService, UserService } from "."
|
import { UserService } from "."
|
||||||
import { User } from ".."
|
import { User } from ".."
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import { UserRoles } from "../models/user"
|
import { UserRoles } from "../models/user"
|
||||||
import { InviteRepository } from "../repositories/invite"
|
import { InviteRepository } from "../repositories/invite"
|
||||||
import { UserRepository } from "../repositories/user"
|
import { UserRepository } from "../repositories/user"
|
||||||
import { ListInvite } from "../types/invites"
|
|
||||||
import { ConfigModule } from "../types/global"
|
import { ConfigModule } from "../types/global"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
import { ListInvite } from "../types/invites"
|
||||||
import { buildQuery } from "../utils"
|
import { buildQuery } from "../utils"
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
|
|
||||||
// 7 days
|
// 7 days
|
||||||
const DEFAULT_VALID_DURATION = 1000 * 60 * 60 * 24 * 7
|
const DEFAULT_VALID_DURATION = 1000 * 60 * 60 * 24 * 7
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import Bull from "bull"
|
import { Job, Queue, Worker } from "bullmq"
|
||||||
import Redis from "ioredis"
|
import Redis from "ioredis"
|
||||||
import { ConfigModule, Logger } from "../types/global"
|
import { ConfigModule, Logger } from "../types/global"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
redisClient: Redis.Redis
|
|
||||||
redisSubscriber: Redis.Redis
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScheduledJobHandler<T = unknown> = (
|
type ScheduledJobHandler<T = unknown> = (
|
||||||
@@ -22,36 +20,34 @@ export default class JobSchedulerService {
|
|||||||
protected readonly logger_: Logger
|
protected readonly logger_: Logger
|
||||||
protected readonly handlers_: Map<string | symbol, ScheduledJobHandler[]> =
|
protected readonly handlers_: Map<string | symbol, ScheduledJobHandler[]> =
|
||||||
new Map()
|
new Map()
|
||||||
protected readonly queue_: Bull
|
protected readonly queue_: Queue
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ logger, redisClient, redisSubscriber }: InjectedDependencies,
|
{ logger }: InjectedDependencies,
|
||||||
config: ConfigModule,
|
config: ConfigModule,
|
||||||
singleton = true
|
singleton = true
|
||||||
) {
|
) {
|
||||||
this.config_ = config
|
this.config_ = config
|
||||||
this.logger_ = logger
|
this.logger_ = logger
|
||||||
|
|
||||||
if (singleton) {
|
if (singleton && config?.projectConfig?.redis_url) {
|
||||||
const opts = {
|
// Required config
|
||||||
createClient: (type: string): Redis.Redis => {
|
// See: https://github.com/OptimalBits/bull/blob/develop/CHANGELOG.md#breaking-changes
|
||||||
switch (type) {
|
const connection = new Redis(config.projectConfig.redis_url, {
|
||||||
case "client":
|
maxRetriesPerRequest: null,
|
||||||
return redisClient
|
enableReadyCheck: false,
|
||||||
case "subscriber":
|
})
|
||||||
return redisSubscriber
|
|
||||||
default:
|
this.queue_ = new Queue(`scheduled-jobs:queue`, {
|
||||||
if (config.projectConfig.redis_url) {
|
connection,
|
||||||
return new Redis(config.projectConfig.redis_url)
|
prefix: `${this.constructor.name}`,
|
||||||
}
|
})
|
||||||
return redisClient
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
this.queue_ = new Bull(`scheduled-jobs:queue`, opts)
|
|
||||||
// Register scheduled job worker
|
// 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,
|
schedule: string,
|
||||||
handler: ScheduledJobHandler,
|
handler: ScheduledJobHandler,
|
||||||
options: CreateJobOptions
|
options: CreateJobOptions
|
||||||
): Promise<void> {
|
): Promise<Job> {
|
||||||
this.logger_.info(`Registering ${eventName}`)
|
this.logger_.info(`Registering ${eventName}`)
|
||||||
this.registerHandler(eventName, handler)
|
this.registerHandler(eventName, handler)
|
||||||
|
|
||||||
@@ -120,10 +116,10 @@ export default class JobSchedulerService {
|
|||||||
eventName,
|
eventName,
|
||||||
data,
|
data,
|
||||||
}
|
}
|
||||||
const repeatOpts = { repeat: { cron: schedule } }
|
const repeatOpts = { repeat: { pattern: schedule } }
|
||||||
|
|
||||||
if (options?.keepExisting) {
|
if (options?.keepExisting) {
|
||||||
return await this.queue_.add(jobToCreate, repeatOpts)
|
return await this.queue_.add(eventName, jobToCreate, repeatOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingJobs = (await this.queue_.getRepeatableJobs()) ?? []
|
const existingJobs = (await this.queue_.getRepeatableJobs()) ?? []
|
||||||
@@ -134,6 +130,6 @@ export default class JobSchedulerService {
|
|||||||
await this.queue_.removeRepeatableByKey(existingJob.key)
|
await this.queue_.removeRepeatableByKey(existingJob.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.queue_.add(jobToCreate, repeatOpts)
|
return await this.queue_.add(eventName, jobToCreate, repeatOpts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
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 { Note } from "../models"
|
||||||
import { buildQuery } from "../utils"
|
import { NoteRepository } from "../repositories/note"
|
||||||
|
import { FindConfig, Selector } from "../types/common"
|
||||||
import { CreateNoteInput } from "../types/note"
|
import { CreateNoteInput } from "../types/note"
|
||||||
|
import { buildQuery } from "../utils"
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { TransactionBaseService } from "../interfaces"
|
import { EventBusTypes } from "@medusajs/types"
|
||||||
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 { MedusaError } from "medusa-core-utils"
|
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 { CreateOrderEditItemChangeInput } from "../types/order-edit"
|
||||||
|
import { buildQuery } from "../utils"
|
||||||
|
import { LineItemService } from "./index"
|
||||||
|
import TaxProviderService from "./tax-provider"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
orderItemChangeRepository: typeof OrderItemChangeRepository
|
orderItemChangeRepository: typeof OrderItemChangeRepository
|
||||||
eventBusService: EventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
lineItemService: LineItemService
|
lineItemService: LineItemService
|
||||||
taxProviderService: TaxProviderService
|
taxProviderService: TaxProviderService
|
||||||
}
|
}
|
||||||
@@ -25,7 +26,7 @@ export default class OrderEditItemChangeService extends TransactionBaseService {
|
|||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
protected readonly orderItemChangeRepository_: typeof OrderItemChangeRepository
|
protected readonly orderItemChangeRepository_: typeof OrderItemChangeRepository
|
||||||
protected readonly eventBus_: EventBusService
|
protected readonly eventBus_: EventBusTypes.IEventBusService
|
||||||
protected readonly lineItemService_: LineItemService
|
protected readonly lineItemService_: LineItemService
|
||||||
protected readonly taxProviderService_: TaxProviderService
|
protected readonly taxProviderService_: TaxProviderService
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import {
|
|||||||
CreateOrderEditInput,
|
CreateOrderEditInput,
|
||||||
} from "../types/order-edit"
|
} from "../types/order-edit"
|
||||||
import { buildQuery, isString } from "../utils"
|
import { buildQuery, isString } from "../utils"
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
import {
|
import {
|
||||||
EventBusService,
|
|
||||||
LineItemAdjustmentService,
|
LineItemAdjustmentService,
|
||||||
LineItemService,
|
LineItemService,
|
||||||
NewTotalsService,
|
NewTotalsService,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
Not,
|
Not,
|
||||||
Raw,
|
Raw,
|
||||||
} from "typeorm"
|
} from "typeorm"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
import { IInventoryService, TransactionBaseService } from "../interfaces"
|
||||||
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
||||||
import {
|
import {
|
||||||
Address,
|
Address,
|
||||||
@@ -50,7 +50,6 @@ import {
|
|||||||
CustomerService,
|
CustomerService,
|
||||||
DiscountService,
|
DiscountService,
|
||||||
DraftOrderService,
|
DraftOrderService,
|
||||||
EventBusService,
|
|
||||||
FulfillmentProviderService,
|
FulfillmentProviderService,
|
||||||
FulfillmentService,
|
FulfillmentService,
|
||||||
GiftCardService,
|
GiftCardService,
|
||||||
@@ -64,6 +63,7 @@ import {
|
|||||||
TaxProviderService,
|
TaxProviderService,
|
||||||
TotalsService
|
TotalsService
|
||||||
} from "."
|
} from "."
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
|
|
||||||
export const ORDER_CART_ALREADY_EXISTS_ERROR = "Order from cart already exists"
|
export const ORDER_CART_ALREADY_EXISTS_ERROR = "Order from cart already exists"
|
||||||
|
|
||||||
@@ -86,6 +86,7 @@ type InjectedDependencies = {
|
|||||||
addressRepository: typeof AddressRepository
|
addressRepository: typeof AddressRepository
|
||||||
giftCardService: GiftCardService
|
giftCardService: GiftCardService
|
||||||
draftOrderService: DraftOrderService
|
draftOrderService: DraftOrderService
|
||||||
|
inventoryService: IInventoryService
|
||||||
eventBusService: EventBusService
|
eventBusService: EventBusService
|
||||||
featureFlagRouter: FlagRouter
|
featureFlagRouter: FlagRouter
|
||||||
productVariantInventoryService: ProductVariantInventoryService
|
productVariantInventoryService: ProductVariantInventoryService
|
||||||
@@ -128,6 +129,7 @@ class OrderService extends TransactionBaseService {
|
|||||||
protected readonly addressRepository_: typeof AddressRepository
|
protected readonly addressRepository_: typeof AddressRepository
|
||||||
protected readonly giftCardService_: GiftCardService
|
protected readonly giftCardService_: GiftCardService
|
||||||
protected readonly draftOrderService_: DraftOrderService
|
protected readonly draftOrderService_: DraftOrderService
|
||||||
|
protected readonly inventoryService_: IInventoryService
|
||||||
protected readonly eventBus_: EventBusService
|
protected readonly eventBus_: EventBusService
|
||||||
protected readonly featureFlagRouter_: FlagRouter
|
protected readonly featureFlagRouter_: FlagRouter
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
import { DeepPartial, EntityManager } from "typeorm"
|
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
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 {
|
import {
|
||||||
PaymentCollection,
|
PaymentCollection,
|
||||||
PaymentCollectionStatus,
|
PaymentCollectionStatus,
|
||||||
PaymentSession,
|
PaymentSession,
|
||||||
PaymentSessionStatus,
|
PaymentSessionStatus,
|
||||||
} from "../models"
|
} from "../models"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
import { PaymentCollectionRepository } from "../repositories/payment-collection"
|
||||||
import {
|
import { FindConfig } from "../types/common"
|
||||||
CustomerService,
|
import { buildQuery, isString, setMetadata } from "../utils"
|
||||||
EventBusService,
|
import { CustomerService, PaymentProviderService } from "./index"
|
||||||
PaymentProviderService,
|
|
||||||
} from "./index"
|
|
||||||
|
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { CreatePaymentInput, PaymentSessionInput } from "../types/payment"
|
||||||
import {
|
import {
|
||||||
CreatePaymentCollectionInput,
|
CreatePaymentCollectionInput,
|
||||||
PaymentCollectionsSessionsBatchInput,
|
PaymentCollectionsSessionsBatchInput,
|
||||||
PaymentCollectionsSessionsInput,
|
PaymentCollectionsSessionsInput,
|
||||||
} from "../types/payment-collection"
|
} from "../types/payment-collection"
|
||||||
import { CreatePaymentInput, PaymentSessionInput } from "../types/payment"
|
import EventBusService from "./event-bus"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { PaymentRepository } from "./../repositories/payment"
|
|
||||||
import { EntityManager } from "typeorm"
|
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
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 { TransactionBaseService } from "../interfaces"
|
||||||
import { EventBusService, PaymentProviderService } from "./index"
|
import { Payment, Refund } from "../models"
|
||||||
import { buildQuery } from "../utils"
|
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig } from "../types/common"
|
||||||
|
import { buildQuery } from "../utils"
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
|
import { PaymentProviderService } from "./index"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
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 { TransactionBaseService } from "../interfaces"
|
||||||
import { ProductCategory } from "../models"
|
import { ProductCategory } from "../models"
|
||||||
import { ProductCategoryRepository } from "../repositories/product-category"
|
import { ProductCategoryRepository } from "../repositories/product-category"
|
||||||
import {
|
import {
|
||||||
FindConfig,
|
FindConfig,
|
||||||
QuerySelector,
|
QuerySelector,
|
||||||
TreeQuerySelector,
|
|
||||||
Selector,
|
Selector,
|
||||||
|
TreeQuerySelector,
|
||||||
} from "../types/common"
|
} from "../types/common"
|
||||||
import { buildQuery, nullableValue } from "../utils"
|
|
||||||
import { EventBusService } from "."
|
|
||||||
import {
|
import {
|
||||||
CreateProductCategoryInput,
|
CreateProductCategoryInput,
|
||||||
UpdateProductCategoryInput,
|
|
||||||
ReorderConditions,
|
ReorderConditions,
|
||||||
tempReorderRank,
|
tempReorderRank,
|
||||||
|
UpdateProductCategoryInput,
|
||||||
} from "../types/product-category"
|
} from "../types/product-category"
|
||||||
import { isNumber } from "lodash"
|
import { buildQuery, nullableValue } from "../utils"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { FlagRouter } from "../utils/flag-router"
|
|
||||||
|
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { ProductVariantService, SearchService } from "."
|
import { ProductVariantService, SearchService } from "."
|
||||||
@@ -31,6 +29,7 @@ import {
|
|||||||
UpdateProductInput,
|
UpdateProductInput,
|
||||||
} from "../types/product"
|
} from "../types/product"
|
||||||
import { buildQuery, isString, setMetadata } from "../utils"
|
import { buildQuery, isString, setMetadata } from "../utils"
|
||||||
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
import EventBusService from "./event-bus"
|
import EventBusService from "./event-bus"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { EntityManager, FindOptionsWhere, ILike } from "typeorm"
|
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
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 { TransactionBaseService } from "../interfaces"
|
||||||
import EventBusService from "./event-bus"
|
import { PublishableApiKey, SalesChannel } from "../models"
|
||||||
import { buildQuery, isString } from "../utils"
|
import { PublishableApiKeyRepository } from "../repositories/publishable-api-key"
|
||||||
|
import { PublishableApiKeySalesChannelRepository } from "../repositories/publishable-api-key-sales-channel"
|
||||||
|
import { FindConfig, Selector } from "../types/common"
|
||||||
import {
|
import {
|
||||||
CreatePublishableApiKeyInput,
|
CreatePublishableApiKeyInput,
|
||||||
UpdatePublishableApiKeyInput,
|
UpdatePublishableApiKeyInput,
|
||||||
} from "../types/publishable-api-key"
|
} 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 = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
|
||||||
import { EventBusService, SalesChannelLocationService } from "./"
|
|
||||||
import { IInventoryService, TransactionBaseService } from "../interfaces"
|
import { IInventoryService, TransactionBaseService } from "../interfaces"
|
||||||
|
|
||||||
|
import { EventBusTypes } from "@medusajs/types"
|
||||||
|
import { SalesChannelLocationService } from "./"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
inventoryService: IInventoryService
|
inventoryService: IInventoryService
|
||||||
salesChannelLocationService: SalesChannelLocationService
|
salesChannelLocationService: SalesChannelLocationService
|
||||||
eventBusService: EventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
}
|
}
|
||||||
|
|
||||||
class SalesChannelInventoryService extends TransactionBaseService {
|
class SalesChannelInventoryService extends TransactionBaseService {
|
||||||
protected readonly salesChannelLocationService_: SalesChannelLocationService
|
protected readonly salesChannelLocationService_: SalesChannelLocationService
|
||||||
protected readonly eventBusService_: EventBusService
|
protected readonly eventBusService_: EventBusTypes.IEventBusService
|
||||||
protected readonly inventoryService_: IInventoryService
|
protected readonly inventoryService_: IInventoryService
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { EntityManager, In } from "typeorm"
|
import { EntityManager, In } from "typeorm"
|
||||||
import { IStockLocationService, TransactionBaseService } from "../interfaces"
|
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 { MedusaError } from "medusa-core-utils"
|
||||||
|
import { SalesChannelLocation } from "../models/sales-channel-location"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
stockLocationService: IStockLocationService
|
stockLocationService: IStockLocationService
|
||||||
salesChannelService: SalesChannelService
|
salesChannelService: SalesChannelService
|
||||||
eventBusService: EventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +20,7 @@ type InjectedDependencies = {
|
|||||||
|
|
||||||
class SalesChannelLocationService extends TransactionBaseService {
|
class SalesChannelLocationService extends TransactionBaseService {
|
||||||
protected readonly salesChannelService_: SalesChannelService
|
protected readonly salesChannelService_: SalesChannelService
|
||||||
protected readonly eventBusService_: EventBusService
|
protected readonly eventBusService_: EventBusTypes.IEventBusService
|
||||||
protected readonly stockLocationService_: IStockLocationService
|
protected readonly stockLocationService_: IStockLocationService
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
|
import { FindConfig, QuerySelector, Selector } from "../types/common"
|
||||||
import {
|
import {
|
||||||
CreateSalesChannelInput,
|
CreateSalesChannelInput,
|
||||||
UpdateSalesChannelInput,
|
UpdateSalesChannelInput,
|
||||||
} from "../types/sales-channels"
|
} 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 { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import { SalesChannel } from "../models"
|
import { SalesChannel } from "../models"
|
||||||
import { SalesChannelRepository } from "../repositories/sales-channel"
|
import { SalesChannelRepository } from "../repositories/sales-channel"
|
||||||
import StoreService from "./store"
|
|
||||||
import { TransactionBaseService } from "../interfaces"
|
|
||||||
import { buildQuery } from "../utils"
|
import { buildQuery } from "../utils"
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
|
import StoreService from "./store"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
salesChannelRepository: typeof SalesChannelRepository
|
salesChannelRepository: typeof SalesChannelRepository
|
||||||
|
|||||||
63
packages/medusa/src/services/staged-job.ts
Normal file
63
packages/medusa/src/services/staged-job.ts
Normal file
@@ -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
|
||||||
@@ -23,11 +23,12 @@ import { SwapRepository } from "../repositories/swap"
|
|||||||
import { FindConfig, Selector, WithRequiredProperty } from "../types/common"
|
import { FindConfig, Selector, WithRequiredProperty } from "../types/common"
|
||||||
import { CreateShipmentConfig } from "../types/fulfillment"
|
import { CreateShipmentConfig } from "../types/fulfillment"
|
||||||
import { OrdersReturnItem } from "../types/orders"
|
import { OrdersReturnItem } from "../types/orders"
|
||||||
import CartService from "./cart"
|
|
||||||
import {
|
import {
|
||||||
|
CartService,
|
||||||
CustomShippingOptionService,
|
CustomShippingOptionService,
|
||||||
EventBusService,
|
EventBusService,
|
||||||
FulfillmentService,
|
FulfillmentService,
|
||||||
|
LineItemAdjustmentService,
|
||||||
LineItemService,
|
LineItemService,
|
||||||
OrderService,
|
OrderService,
|
||||||
PaymentProviderService,
|
PaymentProviderService,
|
||||||
@@ -36,7 +37,6 @@ import {
|
|||||||
ShippingOptionService,
|
ShippingOptionService,
|
||||||
TotalsService,
|
TotalsService,
|
||||||
} from "./index"
|
} from "./index"
|
||||||
import LineItemAdjustmentService from "./line-item-adjustment"
|
|
||||||
|
|
||||||
type InjectedProps = {
|
type InjectedProps = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
@@ -44,7 +44,6 @@ type InjectedProps = {
|
|||||||
swapRepository: typeof SwapRepository
|
swapRepository: typeof SwapRepository
|
||||||
|
|
||||||
cartService: CartService
|
cartService: CartService
|
||||||
eventBus: EventBusService
|
|
||||||
orderService: OrderService
|
orderService: OrderService
|
||||||
returnService: ReturnService
|
returnService: ReturnService
|
||||||
totalsService: TotalsService
|
totalsService: TotalsService
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
import { AwilixContainer } from "awilix"
|
import { AwilixContainer } from "awilix"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { In } from "typeorm"
|
import { In } from "typeorm"
|
||||||
|
|
||||||
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
|
import {
|
||||||
import { ShippingMethodTaxLineRepository } from "../repositories/shipping-method-tax-line"
|
ICacheService,
|
||||||
import { TaxProviderRepository } from "../repositories/tax-provider"
|
ITaxService,
|
||||||
|
ItemTaxCalculationLine,
|
||||||
|
TaxCalculationContext,
|
||||||
|
TransactionBaseService,
|
||||||
|
} from "../interfaces"
|
||||||
import {
|
import {
|
||||||
Cart,
|
Cart,
|
||||||
LineItem,
|
LineItem,
|
||||||
@@ -14,19 +18,15 @@ import {
|
|||||||
ShippingMethodTaxLine,
|
ShippingMethodTaxLine,
|
||||||
TaxProvider,
|
TaxProvider,
|
||||||
} from "../models"
|
} 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 { isCart } from "../types/cart"
|
||||||
import {
|
|
||||||
ICacheService,
|
|
||||||
ITaxService,
|
|
||||||
ItemTaxCalculationLine,
|
|
||||||
TaxCalculationContext,
|
|
||||||
TransactionBaseService,
|
|
||||||
} from "../interfaces"
|
|
||||||
|
|
||||||
import { TaxLinesMaps, TaxServiceRate } from "../types/tax-service"
|
import { TaxLinesMaps, TaxServiceRate } from "../types/tax-service"
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
|
|
||||||
import TaxRateService from "./tax-rate"
|
import TaxRateService from "./tax-rate"
|
||||||
import EventBusService from "./event-bus"
|
|
||||||
|
|
||||||
type RegionDetails = {
|
type RegionDetails = {
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@@ -6,16 +6,15 @@ import {
|
|||||||
orderExportPropertiesDescriptors,
|
orderExportPropertiesDescriptors,
|
||||||
} from "."
|
} from "."
|
||||||
import { AdminPostBatchesReq } from "../../../api"
|
import { AdminPostBatchesReq } from "../../../api"
|
||||||
import { IFileService } from "../../../interfaces"
|
import { AbstractBatchJobStrategy, IFileService } from "../../../interfaces"
|
||||||
import { AbstractBatchJobStrategy } from "../../../interfaces"
|
import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels"
|
||||||
import { Order } from "../../../models"
|
import { Order } from "../../../models"
|
||||||
import { OrderService } from "../../../services"
|
import { OrderService } from "../../../services"
|
||||||
import BatchJobService from "../../../services/batch-job"
|
import BatchJobService from "../../../services/batch-job"
|
||||||
import { BatchJobStatus } from "../../../types/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 { FindConfig } from "../../../types/common"
|
||||||
|
import { FlagRouter } from "../../../utils/flag-router"
|
||||||
|
import { prepareListQuery } from "../../../utils/get-query-config"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
fileService: IFileService
|
fileService: IFileService
|
||||||
|
|||||||
@@ -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 { EntityManager } from "typeorm"
|
||||||
|
import {
|
||||||
|
BatchJobService,
|
||||||
|
EventBusService,
|
||||||
|
StrategyResolverService,
|
||||||
|
} from "../services"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
eventBusService: EventBusService
|
eventBusService: EventBusService
|
||||||
@@ -27,9 +29,15 @@ class BatchJobSubscriber {
|
|||||||
this.strategyResolver_ = strategyResolverService
|
this.strategyResolver_ = strategyResolverService
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
|
|
||||||
this.eventBusService_
|
this.eventBusService_.subscribe(
|
||||||
.subscribe(BatchJobService.Events.CREATED, this.preProcessBatchJob)
|
BatchJobService.Events.CREATED,
|
||||||
.subscribe(BatchJobService.Events.CONFIRMED, this.processBatchJob)
|
this.preProcessBatchJob
|
||||||
|
) as EventBusService
|
||||||
|
|
||||||
|
this.eventBusService_.subscribe(
|
||||||
|
BatchJobService.Events.CONFIRMED,
|
||||||
|
this.processBatchJob
|
||||||
|
) as EventBusService
|
||||||
}
|
}
|
||||||
|
|
||||||
preProcessBatchJob = async (data): Promise<void> => {
|
preProcessBatchJob = async (data): Promise<void> => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import EventBusService from "../services/event-bus"
|
|
||||||
import { CartService } from "../services"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
import { CartService, EventBusService } from "../services"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
eventBusService: EventBusService
|
eventBusService: EventBusService
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
|
import { EventBusTypes } from "@medusajs/types"
|
||||||
import { indexTypes } from "medusa-core-utils"
|
import { indexTypes } from "medusa-core-utils"
|
||||||
import { ISearchService } from "../interfaces"
|
import { ISearchService } from "../interfaces"
|
||||||
import ProductCategoryFeatureFlag from "../loaders/feature-flags/product-categories"
|
import ProductCategoryFeatureFlag from "../loaders/feature-flags/product-categories"
|
||||||
import { SEARCH_INDEX_EVENT } from "../loaders/search-index"
|
import { SEARCH_INDEX_EVENT } from "../loaders/search-index"
|
||||||
import { Product } from "../models"
|
import { Product } from "../models"
|
||||||
import EventBusService from "../services/event-bus"
|
|
||||||
import ProductService from "../services/product"
|
import ProductService from "../services/product"
|
||||||
import { FlagRouter } from "../utils/flag-router"
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
eventBusService: EventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
searchService: ISearchService
|
searchService: ISearchService
|
||||||
productService: ProductService
|
productService: ProductService
|
||||||
featureFlagRouter: FlagRouter
|
featureFlagRouter: FlagRouter
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchIndexingSubscriber {
|
class SearchIndexingSubscriber {
|
||||||
private readonly eventBusService_: EventBusService
|
private readonly eventBusService_: EventBusTypes.IEventBusService
|
||||||
private readonly searchService_: ISearchService
|
private readonly searchService_: ISearchService
|
||||||
private readonly productService_: ProductService
|
private readonly productService_: ProductService
|
||||||
private readonly featureFlagRouter_: FlagRouter
|
private readonly featureFlagRouter_: FlagRouter
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import {
|
import { CommonTypes } from "@medusajs/types"
|
||||||
ExternalModuleDeclaration,
|
|
||||||
InternalModuleDeclaration,
|
|
||||||
} from "@medusajs/modules-sdk"
|
|
||||||
import { Request } from "express"
|
import { Request } from "express"
|
||||||
import { MedusaContainer as coreMedusaContainer } from "medusa-core-utils"
|
import { MedusaContainer as coreMedusaContainer } from "medusa-core-utils"
|
||||||
import { LoggerOptions } from "typeorm"
|
|
||||||
import { Logger as _Logger } from "winston"
|
import { Logger as _Logger } from "winston"
|
||||||
import { Customer, User } from "../models"
|
import { Customer, User } from "../models"
|
||||||
import { EmitOptions } from "../services/event-bus"
|
|
||||||
import { FindConfig, RequestQueryFields } from "./common"
|
import { FindConfig, RequestQueryFields } from "./common"
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -44,67 +39,4 @@ export type Logger = _Logger & {
|
|||||||
|
|
||||||
export type Constructor<T> = new (...args: any[]) => T
|
export type Constructor<T> = new (...args: any[]) => T
|
||||||
|
|
||||||
type SessionOptions = {
|
export type ConfigModule = CommonTypes.ConfigModule
|
||||||
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
|
|
||||||
)[]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
export async function sleep(ms: number) {
|
import { promisify } from "util"
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, ms)
|
export const sleep = promisify(setTimeout)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import { ModuleDefinition, MODULE_RESOURCE_TYPE, MODULE_SCOPE } from "./types"
|
import { ModuleDefinition, MODULE_RESOURCE_TYPE, MODULE_SCOPE } from "./types"
|
||||||
|
|
||||||
export const MODULE_DEFINITIONS: ModuleDefinition[] = [
|
export const MODULE_DEFINITIONS: ModuleDefinition[] = [
|
||||||
|
{
|
||||||
|
key: "eventBus",
|
||||||
|
registrationName: "eventBusModuleService",
|
||||||
|
defaultPackage: "@medusajs/event-bus-local",
|
||||||
|
label: "EventBusModuleService",
|
||||||
|
canOverride: true,
|
||||||
|
isRequired: true,
|
||||||
|
defaultModuleDeclaration: {
|
||||||
|
scope: MODULE_SCOPE.INTERNAL,
|
||||||
|
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "stockLocationService",
|
key: "stockLocationService",
|
||||||
registrationName: "stockLocationService",
|
registrationName: "stockLocationService",
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { IEventBusService, IStockLocationService } from "@medusajs/medusa"
|
import { IStockLocationService } from "@medusajs/medusa"
|
||||||
import {
|
import {
|
||||||
ExternalModuleDeclaration,
|
ExternalModuleDeclaration,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
MedusaModule,
|
MedusaModule
|
||||||
} from "@medusajs/modules-sdk"
|
} from "@medusajs/modules-sdk"
|
||||||
|
import { EventBusTypes } from "@medusajs/types"
|
||||||
import { StockLocationServiceInitializeOptions } from "../types"
|
import { StockLocationServiceInitializeOptions } from "../types"
|
||||||
|
|
||||||
export const initialize = async (
|
export const initialize = async (
|
||||||
options?: StockLocationServiceInitializeOptions | ExternalModuleDeclaration,
|
options?: StockLocationServiceInitializeOptions | ExternalModuleDeclaration,
|
||||||
injectedDependencies?: {
|
injectedDependencies?: {
|
||||||
eventBusService: IEventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
}
|
}
|
||||||
): Promise<IStockLocationService> => {
|
): Promise<IStockLocationService> => {
|
||||||
const serviceKey = "stockLocationService"
|
const serviceKey = "stockLocationService"
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ import {
|
|||||||
CreateStockLocationInput,
|
CreateStockLocationInput,
|
||||||
FilterableStockLocationProps,
|
FilterableStockLocationProps,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
IEventBusService,
|
|
||||||
setMetadata,
|
setMetadata,
|
||||||
StockLocationAddressInput,
|
StockLocationAddressInput,
|
||||||
UpdateStockLocationInput,
|
UpdateStockLocationInput
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { InternalModuleDeclaration } from "@medusajs/modules-sdk"
|
import { InternalModuleDeclaration } from "@medusajs/modules-sdk"
|
||||||
import { SharedContext } from "@medusajs/types"
|
import { EventBusTypes, SharedContext } from "@medusajs/types"
|
||||||
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
@@ -17,7 +16,7 @@ import { StockLocation, StockLocationAddress } from "../models"
|
|||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
eventBusService: IEventBusService
|
eventBusService: EventBusTypes.IEventBusService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +31,7 @@ export default class StockLocationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected readonly manager_: EntityManager
|
protected readonly manager_: EntityManager
|
||||||
protected readonly eventBusService_: IEventBusService
|
protected readonly eventBusService_: EventBusTypes.IEventBusService
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ eventBusService, manager }: InjectedDependencies,
|
{ eventBusService, manager }: InjectedDependencies,
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
],
|
],
|
||||||
"author": "Medusa",
|
"author": "Medusa",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@medusajs/modules-sdk": "^0.0.1"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-env": "^5.2.1",
|
"cross-env": "^5.2.1",
|
||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
@@ -24,6 +27,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "cross-env NODE_ENV=production yarn run build",
|
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||||
"build": "tsc --build",
|
"build": "tsc --build",
|
||||||
|
"watch": "tsc --build --watch",
|
||||||
"test": "exit 0"
|
"test": "exit 0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
packages/types/src/bundles.ts
Normal file
4
packages/types/src/bundles.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * as CommonTypes from "./common"
|
||||||
|
export * as EventBusTypes from "./event-bus"
|
||||||
|
export * as TransactionBaseTypes from "./transaction-base"
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user