chore: local workflow proxying methods to pass context (#6263)

What:
- When calling a module's method inside a Local Workflow the MedusaContext is passed as the last argument to the method if not provided
- Add `requestId` to req
- A couple of fixes on Remote Joiner and the data fetcher for internal services

Why:
- The context used to initialize the workflow has to be shared with all modules. properties like transactionId will be used to emit events and requestId to trace logs for example.
This commit is contained in:
Carlos R. L. Rodrigues
2024-02-01 13:37:26 +00:00
committed by GitHub
parent a2bf6756ac
commit 45134e4d11
40 changed files with 576 additions and 170 deletions
+15
View File
@@ -0,0 +1,15 @@
---
"@medusajs/stock-location": patch
"@medusajs/orchestration": patch
"@medusajs/workflows-sdk": patch
"@medusajs/link-modules": patch
"@medusajs/modules-sdk": patch
"@medusajs/inventory": patch
"@medusajs/pricing": patch
"@medusajs/product": patch
"@medusajs/medusa": patch
"@medusajs/types": patch
"@medusajs/utils": patch
---
Workflows passing MedusaContext as argument
@@ -100,4 +100,98 @@ describe("POST /admin/campaigns", () => {
}) })
) )
}) })
it("should create 3 campaigns in parallel and have the context passed as argument when calling createCampaigns with different transactionId", async () => {
const parallelPromotion = await promotionModuleService.create({
code: "PARALLEL",
type: "standard",
})
const spyCreateCampaigns = jest.spyOn(
promotionModuleService.constructor.prototype,
"createCampaigns"
)
const api = useApi() as any
const a = async () => {
return await api.post(
`/admin/campaigns`,
{
name: "camp_1",
campaign_identifier: "camp_1",
starts_at: new Date("01/01/2024").toISOString(),
ends_at: new Date("01/02/2024").toISOString(),
promotions: [{ id: parallelPromotion.id }],
budget: {
limit: 1000,
type: "usage",
},
},
adminHeaders
)
}
const b = async () => {
return await api.post(
`/admin/campaigns`,
{
name: "camp_2",
campaign_identifier: "camp_2",
starts_at: new Date("01/02/2024").toISOString(),
ends_at: new Date("01/03/2029").toISOString(),
promotions: [{ id: parallelPromotion.id }],
budget: {
limit: 500,
type: "usage",
},
},
adminHeaders
)
}
const c = async () => {
return await api.post(
`/admin/campaigns`,
{
name: "camp_3",
campaign_identifier: "camp_3",
starts_at: new Date("01/03/2024").toISOString(),
ends_at: new Date("01/04/2029").toISOString(),
promotions: [{ id: parallelPromotion.id }],
budget: {
limit: 250,
type: "usage",
},
},
{
headers: {
...adminHeaders.headers,
"x-request-id": "my-custom-request-id",
},
}
)
}
await Promise.all([a(), b(), c()])
expect(spyCreateCampaigns).toHaveBeenCalledTimes(3)
expect(spyCreateCampaigns.mock.calls[0][1].__type).toBe("MedusaContext")
const distinctTransactionId = [
...new Set(
spyCreateCampaigns.mock.calls.map((call) => call[1].transactionId)
),
]
expect(distinctTransactionId).toHaveLength(3)
const distinctRequestId = [
...new Set(
spyCreateCampaigns.mock.calls.map((call) => call[1].requestId)
),
]
expect(distinctRequestId).toHaveLength(3)
expect(distinctRequestId).toContain("my-custom-request-id")
})
}) })
@@ -1,15 +1,15 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IProductModuleService, WorkflowTypes } from "@medusajs/types"
import { import {
createProducts,
CreateProductsActions, CreateProductsActions,
Handlers, Handlers,
createProducts,
} from "@medusajs/core-flows" } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IProductModuleService, WorkflowTypes } from "@medusajs/types"
import { pipe } from "@medusajs/workflows-sdk"
import path from "path" import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app" import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { getContainer } from "../../../../environment-helpers/use-container" import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db" import { initDb, useDb } from "../../../../environment-helpers/use-db"
import { pipe } from "@medusajs/workflows-sdk"
jest.setTimeout(30000) jest.setTimeout(30000)
@@ -25,8 +25,6 @@ describe("CreateProduct workflow", function () {
}) })
afterAll(async () => { afterAll(async () => {
console.log("GLOABL GC()", typeof global)
const db = useDb() const db = useDb()
await db.shutdown() await db.shutdown()
await shutdownServer() await shutdownServer()
+14 -19
View File
@@ -1,23 +1,24 @@
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import { import {
AuthProviderDTO,
AuthTypes,
AuthUserDTO,
AuthenticationInput, AuthenticationInput,
AuthenticationResponse, AuthenticationResponse,
AuthTypes,
Context, Context,
CreateAuthProviderDTO,
CreateAuthUserDTO,
DAL, DAL,
FilterableAuthProviderProps,
FilterableAuthUserProps,
FindConfig, FindConfig,
InternalModuleDeclaration, InternalModuleDeclaration,
JWTGenerationOptions,
MedusaContainer, MedusaContainer,
ModuleJoinerConfig, ModuleJoinerConfig,
JWTGenerationOptions, UpdateAuthUserDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { AuthProvider, AuthUser } from "@models"
import { joinerConfig } from "../joiner-config"
import { AuthProviderService, AuthUserService } from "@services"
import { import {
AbstractAuthModuleProvider, AbstractAuthModuleProvider,
InjectManager, InjectManager,
@@ -25,16 +26,10 @@ import {
MedusaContext, MedusaContext,
MedusaError, MedusaError,
} from "@medusajs/utils" } from "@medusajs/utils"
import { import { AuthProvider, AuthUser } from "@models"
AuthProviderDTO, import { AuthProviderService, AuthUserService } from "@services"
AuthUserDTO,
CreateAuthProviderDTO,
CreateAuthUserDTO,
FilterableAuthProviderProps,
FilterableAuthUserProps,
UpdateAuthUserDTO,
} from "@medusajs/types"
import { ServiceTypes } from "@types" import { ServiceTypes } from "@types"
import { joinerConfig } from "../joiner-config"
type AuthModuleOptions = { type AuthModuleOptions = {
jwt_secret: string jwt_secret: string
@@ -90,7 +85,7 @@ export default class AuthModuleService<
async retrieveAuthProvider( async retrieveAuthProvider(
provider: string, provider: string,
config: FindConfig<AuthProviderDTO> = {}, config: FindConfig<AuthProviderDTO> = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<AuthProviderDTO> { ): Promise<AuthProviderDTO> {
const authProvider = await this.authProviderService_.retrieve( const authProvider = await this.authProviderService_.retrieve(
provider, provider,
@@ -107,7 +102,7 @@ export default class AuthModuleService<
async listAuthProviders( async listAuthProviders(
filters: FilterableAuthProviderProps = {}, filters: FilterableAuthProviderProps = {},
config: FindConfig<AuthProviderDTO> = {}, config: FindConfig<AuthProviderDTO> = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<AuthProviderDTO[]> { ): Promise<AuthProviderDTO[]> {
const authProviders = await this.authProviderService_.list( const authProviders = await this.authProviderService_.list(
filters, filters,
@@ -8,14 +8,14 @@ import {
} from "@medusajs/types" } from "@medusajs/types"
import { import {
InjectEntityManager, InjectEntityManager,
isDefined,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
isDefined,
} from "@medusajs/utils" } from "@medusajs/utils"
import { DeepPartial, EntityManager, FindManyOptions, In } from "typeorm" import { DeepPartial, EntityManager, FindManyOptions, In } from "typeorm"
import { InventoryItem } from "../models" import { InventoryItem } from "../models"
import { getListQuery } from "../utils/query"
import { buildQuery } from "../utils/build-query" import { buildQuery } from "../utils/build-query"
import { getListQuery } from "../utils/query"
type InjectedDependencies = { type InjectedDependencies = {
eventBusService: IEventBusService eventBusService: IEventBusService
@@ -47,7 +47,7 @@ export default class InventoryItemService {
async list( async list(
selector: FilterableInventoryItemProps = {}, selector: FilterableInventoryItemProps = {},
config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 }, config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<InventoryItemDTO[]> { ): Promise<InventoryItemDTO[]> {
const queryBuilder = getListQuery( const queryBuilder = getListQuery(
context.transactionManager ?? this.manager_, context.transactionManager ?? this.manager_,
@@ -68,7 +68,7 @@ export default class InventoryItemService {
async retrieve( async retrieve(
inventoryItemId: string, inventoryItemId: string,
config: FindConfig<InventoryItem> = {}, config: FindConfig<InventoryItem> = {},
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<InventoryItem> { ): Promise<InventoryItem> {
if (!isDefined(inventoryItemId)) { if (!isDefined(inventoryItemId)) {
throw new MedusaError( throw new MedusaError(
@@ -102,7 +102,7 @@ export default class InventoryItemService {
async listAndCount( async listAndCount(
selector: FilterableInventoryItemProps = {}, selector: FilterableInventoryItemProps = {},
config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 }, config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<[InventoryItemDTO[], number]> { ): Promise<[InventoryItemDTO[], number]> {
const queryBuilder = getListQuery( const queryBuilder = getListQuery(
context.transactionManager ?? this.manager_, context.transactionManager ?? this.manager_,
@@ -7,9 +7,9 @@ import {
} from "@medusajs/types" } from "@medusajs/types"
import { import {
InjectEntityManager, InjectEntityManager,
isDefined,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
isDefined,
} from "@medusajs/utils" } from "@medusajs/utils"
import { DeepPartial, EntityManager, FindManyOptions, In } from "typeorm" import { DeepPartial, EntityManager, FindManyOptions, In } from "typeorm"
import { InventoryLevel } from "../models" import { InventoryLevel } from "../models"
@@ -46,7 +46,7 @@ export default class InventoryLevelService {
async list( async list(
selector: FilterableInventoryLevelProps = {}, selector: FilterableInventoryLevelProps = {},
config: FindConfig<InventoryLevel> = { relations: [], skip: 0, take: 10 }, config: FindConfig<InventoryLevel> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<InventoryLevel[]> { ): Promise<InventoryLevel[]> {
const manager = context.transactionManager ?? this.manager_ const manager = context.transactionManager ?? this.manager_
const levelRepository = manager.getRepository(InventoryLevel) const levelRepository = manager.getRepository(InventoryLevel)
@@ -65,7 +65,7 @@ export default class InventoryLevelService {
async listAndCount( async listAndCount(
selector: FilterableInventoryLevelProps = {}, selector: FilterableInventoryLevelProps = {},
config: FindConfig<InventoryLevel> = { relations: [], skip: 0, take: 10 }, config: FindConfig<InventoryLevel> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<[InventoryLevel[], number]> { ): Promise<[InventoryLevel[], number]> {
const manager = context.transactionManager ?? this.manager_ const manager = context.transactionManager ?? this.manager_
const levelRepository = manager.getRepository(InventoryLevel) const levelRepository = manager.getRepository(InventoryLevel)
@@ -85,7 +85,7 @@ export default class InventoryLevelService {
async retrieve( async retrieve(
inventoryLevelId: string, inventoryLevelId: string,
config: FindConfig<InventoryLevel> = {}, config: FindConfig<InventoryLevel> = {},
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<InventoryLevel> { ): Promise<InventoryLevel> {
if (!isDefined(inventoryLevelId)) { if (!isDefined(inventoryLevelId)) {
throw new MedusaError( throw new MedusaError(
@@ -319,7 +319,7 @@ export default class InventoryLevelService {
async getStockedQuantity( async getStockedQuantity(
inventoryItemId: string, inventoryItemId: string,
locationIds: string[] | string, locationIds: string[] | string,
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<number> { ): Promise<number> {
if (!Array.isArray(locationIds)) { if (!Array.isArray(locationIds)) {
locationIds = [locationIds] locationIds = [locationIds]
@@ -348,7 +348,7 @@ export default class InventoryLevelService {
async getAvailableQuantity( async getAvailableQuantity(
inventoryItemId: string, inventoryItemId: string,
locationIds: string[] | string, locationIds: string[] | string,
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<number> { ): Promise<number> {
if (!Array.isArray(locationIds)) { if (!Array.isArray(locationIds)) {
locationIds = [locationIds] locationIds = [locationIds]
@@ -377,7 +377,7 @@ export default class InventoryLevelService {
async getReservedQuantity( async getReservedQuantity(
inventoryItemId: string, inventoryItemId: string,
locationIds: string[] | string, locationIds: string[] | string,
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<number> { ): Promise<number> {
if (!Array.isArray(locationIds)) { if (!Array.isArray(locationIds)) {
locationIds = [locationIds] locationIds = [locationIds]
+11 -11
View File
@@ -74,7 +74,7 @@ export default class InventoryService implements IInventoryService {
async listInventoryItems( async listInventoryItems(
selector: FilterableInventoryItemProps, selector: FilterableInventoryItemProps,
config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 }, config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<[InventoryItemDTO[], number]> { ): Promise<[InventoryItemDTO[], number]> {
return await this.inventoryItemService_.listAndCount( return await this.inventoryItemService_.listAndCount(
selector, selector,
@@ -85,7 +85,7 @@ export default class InventoryService implements IInventoryService {
async list( async list(
selector: FilterableInventoryItemProps, selector: FilterableInventoryItemProps,
config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 }, config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<InventoryItemDTO[]> { ): Promise<InventoryItemDTO[]> {
return await this.inventoryItemService_.list(selector, config, context) return await this.inventoryItemService_.list(selector, config, context)
} }
@@ -104,7 +104,7 @@ export default class InventoryService implements IInventoryService {
skip: 0, skip: 0,
take: 10, take: 10,
}, },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<[InventoryLevelDTO[], number]> { ): Promise<[InventoryLevelDTO[], number]> {
return await this.inventoryLevelService_.listAndCount( return await this.inventoryLevelService_.listAndCount(
selector, selector,
@@ -127,7 +127,7 @@ export default class InventoryService implements IInventoryService {
skip: 0, skip: 0,
take: 10, take: 10,
}, },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<[ReservationItemDTO[], number]> { ): Promise<[ReservationItemDTO[], number]> {
return await this.reservationItemService_.listAndCount( return await this.reservationItemService_.listAndCount(
selector, selector,
@@ -146,7 +146,7 @@ export default class InventoryService implements IInventoryService {
async retrieveInventoryItem( async retrieveInventoryItem(
inventoryItemId: string, inventoryItemId: string,
config?: FindConfig<InventoryItemDTO>, config?: FindConfig<InventoryItemDTO>,
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<InventoryItemDTO> { ): Promise<InventoryItemDTO> {
const inventoryItem = await this.inventoryItemService_.retrieve( const inventoryItem = await this.inventoryItemService_.retrieve(
inventoryItemId, inventoryItemId,
@@ -166,7 +166,7 @@ export default class InventoryService implements IInventoryService {
async retrieveInventoryLevel( async retrieveInventoryLevel(
inventoryItemId: string, inventoryItemId: string,
locationId: string, locationId: string,
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<InventoryLevelDTO> { ): Promise<InventoryLevelDTO> {
const [inventoryLevel] = await this.inventoryLevelService_.list( const [inventoryLevel] = await this.inventoryLevelService_.list(
{ inventory_item_id: inventoryItemId, location_id: locationId }, { inventory_item_id: inventoryItemId, location_id: locationId },
@@ -191,7 +191,7 @@ export default class InventoryService implements IInventoryService {
*/ */
async retrieveReservationItem( async retrieveReservationItem(
reservationId: string, reservationId: string,
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<ReservationItemDTO> { ): Promise<ReservationItemDTO> {
return await this.reservationItemService_.retrieve( return await this.reservationItemService_.retrieve(
reservationId, reservationId,
@@ -202,7 +202,7 @@ export default class InventoryService implements IInventoryService {
private async ensureInventoryLevels( private async ensureInventoryLevels(
data: { location_id: string; inventory_item_id: string }[], data: { location_id: string; inventory_item_id: string }[],
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<InventoryLevelDTO[]> { ): Promise<InventoryLevelDTO[]> {
const inventoryLevels = await this.inventoryLevelService_.list( const inventoryLevels = await this.inventoryLevelService_.list(
{ {
@@ -629,7 +629,7 @@ export default class InventoryService implements IInventoryService {
async retrieveAvailableQuantity( async retrieveAvailableQuantity(
inventoryItemId: string, inventoryItemId: string,
locationIds: string[], locationIds: string[],
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<number> { ): Promise<number> {
// Throws if item does not exist // Throws if item does not exist
await this.inventoryItemService_.retrieve( await this.inventoryItemService_.retrieve(
@@ -665,7 +665,7 @@ export default class InventoryService implements IInventoryService {
async retrieveStockedQuantity( async retrieveStockedQuantity(
inventoryItemId: string, inventoryItemId: string,
locationIds: string[], locationIds: string[],
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<number> { ): Promise<number> {
// Throws if item does not exist // Throws if item does not exist
await this.inventoryItemService_.retrieve( await this.inventoryItemService_.retrieve(
@@ -701,7 +701,7 @@ export default class InventoryService implements IInventoryService {
async retrieveReservedQuantity( async retrieveReservedQuantity(
inventoryItemId: string, inventoryItemId: string,
locationIds: string[], locationIds: string[],
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<number> { ): Promise<number> {
// Throws if item does not exist // Throws if item does not exist
await this.inventoryItemService_.retrieve( await this.inventoryItemService_.retrieve(
@@ -8,9 +8,9 @@ import {
} from "@medusajs/types" } from "@medusajs/types"
import { import {
InjectEntityManager, InjectEntityManager,
isDefined,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
isDefined,
promiseAll, promiseAll,
} from "@medusajs/utils" } from "@medusajs/utils"
import { EntityManager, FindManyOptions, In } from "typeorm" import { EntityManager, FindManyOptions, In } from "typeorm"
@@ -55,7 +55,7 @@ export default class ReservationItemService {
async list( async list(
selector: FilterableReservationItemProps = {}, selector: FilterableReservationItemProps = {},
config: FindConfig<ReservationItem> = { relations: [], skip: 0, take: 10 }, config: FindConfig<ReservationItem> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<ReservationItem[]> { ): Promise<ReservationItem[]> {
const manager = context.transactionManager ?? this.manager_ const manager = context.transactionManager ?? this.manager_
const itemRepository = manager.getRepository(ReservationItem) const itemRepository = manager.getRepository(ReservationItem)
@@ -75,7 +75,7 @@ export default class ReservationItemService {
async listAndCount( async listAndCount(
selector: FilterableReservationItemProps = {}, selector: FilterableReservationItemProps = {},
config: FindConfig<ReservationItem> = { relations: [], skip: 0, take: 10 }, config: FindConfig<ReservationItem> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<[ReservationItem[], number]> { ): Promise<[ReservationItem[], number]> {
const manager = context.transactionManager ?? this.manager_ const manager = context.transactionManager ?? this.manager_
const itemRepository = manager.getRepository(ReservationItem) const itemRepository = manager.getRepository(ReservationItem)
@@ -96,7 +96,7 @@ export default class ReservationItemService {
async retrieve( async retrieve(
reservationItemId: string, reservationItemId: string,
config: FindConfig<ReservationItem> = {}, config: FindConfig<ReservationItem> = {},
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<ReservationItem> { ): Promise<ReservationItem> {
if (!isDefined(reservationItemId)) { if (!isDefined(reservationItemId)) {
throw new MedusaError( throw new MedusaError(
@@ -11,12 +11,12 @@ import {
import { import {
InjectManager, InjectManager,
InjectTransactionManager, InjectTransactionManager,
isDefined,
mapObjectTo,
MapToConfig, MapToConfig,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
ModulesSdkUtils, ModulesSdkUtils,
isDefined,
mapObjectTo,
} from "@medusajs/utils" } from "@medusajs/utils"
import { LinkService } from "@services" import { LinkService } from "@services"
import { shouldForceTransaction } from "../utils" import { shouldForceTransaction } from "../utils"
@@ -229,7 +229,7 @@ export default class LinkModuleService<TLink> implements ILinkModule {
async softDelete( async softDelete(
data: any, data: any,
{ returnLinkableKeys }: SoftDeleteReturn = {}, { returnLinkableKeys }: SoftDeleteReturn = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<Record<string, unknown[]> | void> { ): Promise<Record<string, unknown[]> | void> {
this.validateFields(data) this.validateFields(data)
@@ -270,7 +270,7 @@ export default class LinkModuleService<TLink> implements ILinkModule {
async restore( async restore(
data: any, data: any,
{ returnLinkableKeys }: RestoreReturn = {}, { returnLinkableKeys }: RestoreReturn = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<Record<string, unknown[]> | void> { ): Promise<Record<string, unknown[]> | void> {
this.validateFields(data) this.validateFields(data)
@@ -30,6 +30,9 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
const { result, errors } = await createCampaigns.run({ const { result, errors } = await createCampaigns.run({
input: { campaignsData }, input: { campaignsData },
throwOnError: false, throwOnError: false,
context: {
requestId: req.requestId,
},
}) })
if (Array.isArray(errors) && errors[0]) { if (Array.isArray(errors) && errors[0]) {
+3 -2
View File
@@ -13,9 +13,9 @@ import { asValue } from "awilix"
import { createMedusaContainer } from "medusa-core-utils" import { createMedusaContainer } from "medusa-core-utils"
import { track } from "medusa-telemetry" import { track } from "medusa-telemetry"
import { EOL } from "os" import { EOL } from "os"
import path from "path"
import requestIp from "request-ip" import requestIp from "request-ip"
import { Connection } from "typeorm" import { Connection } from "typeorm"
import { v4 } from "uuid"
import { MedusaContainer } from "../types/global" import { MedusaContainer } from "../types/global"
import apiLoader from "./api" import apiLoader from "./api"
import loadConfig from "./config" import loadConfig from "./config"
@@ -192,7 +192,8 @@ export default async ({
// Add the registered services to the request scope // Add the registered services to the request scope
expressApp.use((req: Request, res: Response, next: NextFunction) => { expressApp.use((req: Request, res: Response, next: NextFunction) => {
container.register({ manager: asValue(dataSource.manager) }) container.register({ manager: asValue(dataSource.manager) })
;(req as any).scope = container.createScope() req.scope = container.createScope() as MedusaContainer
req.requestId = (req.headers["x-request-id"] as string) ?? v4()
next() next()
}) })
+1
View File
@@ -18,6 +18,7 @@ declare global {
allowedProperties: string[] allowedProperties: string[]
includes?: Record<string, boolean> includes?: Record<string, boolean>
errors: string[] errors: string[]
requestId?: string
} }
} }
} }
+1
View File
@@ -6,6 +6,7 @@ import type { MedusaContainer } from "./global"
export interface MedusaRequest extends Request { export interface MedusaRequest extends Request {
user?: (User | Customer) & { customer_id?: string; userId?: string } user?: (User | Customer) & { customer_id?: string; userId?: string }
scope: MedusaContainer scope: MedusaContainer
requestId?: string
auth_user?: { id: string; app_metadata: Record<string, any>; scope: string } auth_user?: { id: string; app_metadata: Record<string, any>; scope: string }
} }
@@ -1,6 +1,23 @@
import { MedusaModule, RemoteQuery } from "@medusajs/modules-sdk" import { MedusaModule, RemoteQuery } from "@medusajs/modules-sdk"
import { MedusaContainer } from "@medusajs/types" import { MedusaContainer } from "@medusajs/types"
function hasPagination(options: { [attr: string]: unknown }): boolean {
if (!options) {
return false
}
const attrs = ["skip"]
return Object.keys(options).some((key) => attrs.includes(key))
}
function buildPagination(options, count) {
return {
skip: options.skip,
take: options.take,
count,
}
}
export function remoteQueryFetchData(container: MedusaContainer) { export function remoteQueryFetchData(container: MedusaContainer) {
return async (expand, keyField, ids, relationship) => { return async (expand, keyField, ids, relationship) => {
const serviceConfig = expand.serviceConfig const serviceConfig = expand.serviceConfig
@@ -12,11 +29,35 @@ export function remoteQueryFetchData(container: MedusaContainer) {
return return
} }
const filters = {} let filters = {}
const options = { const options = {
...RemoteQuery.getAllFieldsAndRelations(expand), ...RemoteQuery.getAllFieldsAndRelations(expand),
} }
const availableOptions = [
"skip",
"take",
"limit",
"offset",
"order",
"sort",
"withDeleted",
]
const availableOptionsAlias = new Map([
["limit", "take"],
["offset", "skip"],
["sort", "order"],
])
for (const arg of expand.args || []) {
if (arg.name === "filters" && arg.value) {
filters = { ...arg.value }
} else if (availableOptions.includes(arg.name)) {
const argName = availableOptionsAlias.has(arg.name)
? availableOptionsAlias.get(arg.name)
: arg.name
options[argName] = arg.value
}
}
const expandRelations = Object.keys(expand.expands ?? {}) const expandRelations = Object.keys(expand.expands ?? {})
// filter out links from relations because TypeORM will throw if the relation doesn't exist // filter out links from relations because TypeORM will throw if the relation doesn't exist
@@ -33,11 +74,9 @@ export function remoteQueryFetchData(container: MedusaContainer) {
filters[keyField] = ids filters[keyField] = ids
} }
const hasPagination = Object.keys(options).some((key) => const hasPagination_ = hasPagination(options)
["skip"].includes(key)
)
let methodName = hasPagination ? "listAndCount" : "list" let methodName = hasPagination_ ? "listAndCount" : "list"
if (relationship?.args?.methodSuffix) { if (relationship?.args?.methodSuffix) {
methodName += relationship.args.methodSuffix methodName += relationship.args.methodSuffix
@@ -47,12 +86,12 @@ export function remoteQueryFetchData(container: MedusaContainer) {
const result = await service[methodName](filters, options) const result = await service[methodName](filters, options)
if (hasPagination) { if (hasPagination_) {
const [data, count] = result const [data, count] = result
return { return {
data: { data: {
rows: data, rows: data,
metadata: {}, metadata: buildPagination(options, count),
}, },
path: "rows", path: "rows",
} }
@@ -9,6 +9,7 @@ import {
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
createMedusaContainer, createMedusaContainer,
MedusaModuleType,
} from "@medusajs/utils" } from "@medusajs/utils"
import { asFunction, asValue } from "awilix" import { asFunction, asValue } from "awilix"
@@ -19,7 +20,7 @@ export async function loadInternalModule(
): Promise<{ error?: Error } | void> { ): Promise<{ error?: Error } | void> {
const registrationName = resolution.definition.registrationName const registrationName = resolution.definition.registrationName
const { scope, resources } = const { resources } =
resolution.moduleDeclaration as InternalModuleDeclaration resolution.moduleDeclaration as InternalModuleDeclaration
let loadedModule: ModuleExports let loadedModule: ModuleExports
@@ -111,6 +112,7 @@ export async function loadInternalModule(
const moduleService = loadedModule.service const moduleService = loadedModule.service
container.register({ container.register({
[registrationName]: asFunction((cradle) => { [registrationName]: asFunction((cradle) => {
;(moduleService as any).__type = MedusaModuleType
return new moduleService( return new moduleService(
localContainer.cradle, localContainer.cradle,
resolution.options, resolution.options,
+8 -7
View File
@@ -1,3 +1,8 @@
import {
RemoteFetchDataCallback,
RemoteJoiner,
toRemoteJoinerQuery,
} from "@medusajs/orchestration"
import { import {
JoinerRelationship, JoinerRelationship,
JoinerServiceConfig, JoinerServiceConfig,
@@ -6,11 +11,6 @@ import {
RemoteExpandProperty, RemoteExpandProperty,
RemoteJoinerQuery, RemoteJoinerQuery,
} from "@medusajs/types" } from "@medusajs/types"
import {
RemoteFetchDataCallback,
RemoteJoiner,
toRemoteJoinerQuery,
} from "@medusajs/orchestration"
import { isString, toPascalCase } from "@medusajs/utils" import { isString, toPascalCase } from "@medusajs/utils"
import { MedusaModule } from "./medusa-module" import { MedusaModule } from "./medusa-module"
@@ -30,7 +30,7 @@ export class RemoteQuery {
servicesConfig?: ModuleJoinerConfig[] servicesConfig?: ModuleJoinerConfig[]
}) { }) {
const servicesConfig_ = [...servicesConfig] const servicesConfig_ = [...servicesConfig]
if (!modulesLoaded?.length) { if (!modulesLoaded?.length) {
modulesLoaded = MedusaModule.getLoadedModules().map( modulesLoaded = MedusaModule.getLoadedModules().map(
(mod) => Object.values(mod)[0] (mod) => Object.values(mod)[0]
@@ -164,6 +164,7 @@ export class RemoteQuery {
"offset", "offset",
"cursor", "cursor",
"sort", "sort",
"withDeleted",
] ]
const availableOptionsAlias = new Map([ const availableOptionsAlias = new Map([
["limit", "take"], ["limit", "take"],
@@ -228,7 +229,7 @@ export class RemoteQuery {
if (isString(query)) { if (isString(query)) {
finalQuery = RemoteJoiner.parseQuery(query, variables) finalQuery = RemoteJoiner.parseQuery(query, variables)
} else if (!isString(finalQuery?.service) && !isString(finalQuery?.alias)) { } else if (!isString(finalQuery?.service) && !isString(finalQuery?.alias)) {
finalQuery = toRemoteJoinerQuery(query) finalQuery = toRemoteJoinerQuery(query, variables)
} }
return await this.remoteJoiner.query(finalQuery) return await this.remoteJoiner.query(finalQuery)
@@ -144,4 +144,79 @@ describe("toRemoteJoinerQuery", () => {
], ],
}) })
}) })
it("should transform a nested object with arguments and directives to a Remote Joiner Query format only using variables", async () => {
const obj = {
product: {
fields: ["id", "title", "handle"],
variants: {
fields: ["sku"],
__directives: {
directiveName: "value",
},
shipping_profiles: {
profile: {
fields: ["id", "name"],
},
},
},
},
}
const rjQuery = toRemoteJoinerQuery(obj, {
product: {
limit: 10,
offset: 0,
},
"product.variants.shipping_profiles.profile": {
context: {
customer_group: "cg_123",
region_id: "US",
},
},
})
expect(rjQuery).toEqual({
alias: "product",
fields: ["id", "title", "handle"],
expands: [
{
property: "variants",
directives: [
{
name: "directiveName",
value: "value",
},
],
fields: ["sku"],
},
{
property: "variants.shipping_profiles",
},
{
property: "variants.shipping_profiles.profile",
args: [
{
name: "context",
value: {
customer_group: "cg_123",
region_id: "US",
},
},
],
fields: ["id", "name"],
},
],
args: [
{
name: "limit",
value: 10,
},
{
name: "offset",
value: 0,
},
],
})
})
}) })
@@ -37,10 +37,11 @@ class GraphQLParser {
} }
private parseValueNode(valueNode: ValueNode): unknown { private parseValueNode(valueNode: ValueNode): unknown {
const obj = {}
switch (valueNode.kind) { switch (valueNode.kind) {
case Kind.VARIABLE: case Kind.VARIABLE:
const variableName = valueNode.name.value return this.variables ? this.variables[valueNode.name.value] : undefined
return this.variables ? this.variables[variableName] : undefined
case Kind.INT: case Kind.INT:
return parseInt(valueNode.value, 10) return parseInt(valueNode.value, 10)
case Kind.FLOAT: case Kind.FLOAT:
@@ -55,7 +56,6 @@ class GraphQLParser {
case Kind.LIST: case Kind.LIST:
return valueNode.values.map((v) => this.parseValueNode(v)) return valueNode.values.map((v) => this.parseValueNode(v))
case Kind.OBJECT: case Kind.OBJECT:
let obj = {}
for (const field of valueNode.fields) { for (const field of valueNode.fields) {
obj[field.name.value] = this.parseValueNode(field.value) obj[field.name.value] = this.parseValueNode(field.value)
} }
+63 -35
View File
@@ -1,53 +1,81 @@
import { RemoteJoinerQuery } from "@medusajs/types" import { RemoteJoinerQuery } from "@medusajs/types"
export function toRemoteJoinerQuery(obj: any): RemoteJoinerQuery { export function toRemoteJoinerQuery(
obj: any,
variables: Record<string, any> = {}
): RemoteJoinerQuery {
const remoteJoinerQuery: RemoteJoinerQuery = { const remoteJoinerQuery: RemoteJoinerQuery = {
alias: "", alias: "",
fields: [], fields: [],
expands: [], expands: [],
} }
function extractRecursive(obj, parentName = "", isEntryPoint = true) { let entryPoint = ""
for (const key in obj) { function extractRecursive(obj: any, parentName = "", isEntryPoint = true) {
for (const key of Object.keys(obj ?? {})) {
const value = obj[key] const value = obj[key]
const canExpand = const canExpand =
typeof value === "object" && typeof value === "object" &&
!["fields", "__args", "__directives"].includes(key) !["fields", "__args", "__directives"].includes(key)
if (canExpand) { if (!canExpand) {
const entityName = parentName ? `${parentName}.${key}` : key continue
const expandObj: any = {
property: entityName,
}
const reference = isEntryPoint ? remoteJoinerQuery : expandObj
if (value.__args) {
reference.args = Object.entries(value.__args).map(
([name, value]) => ({
name,
value,
})
)
}
if (value.__directives) {
reference.directives = Object.entries(value.__directives).map(
([name, value]) => ({ name, value })
)
}
reference.fields = value.fields
if (isEntryPoint) {
remoteJoinerQuery.alias = key
} else {
remoteJoinerQuery.expands!.push(expandObj)
}
extractRecursive(value, isEntryPoint ? "" : entityName, false)
} }
const entityName = parentName ? `${parentName}.${key}` : key
const variablesPath = !isEntryPoint
? `${entryPoint}${parentName ? "." + parentName : parentName}.${key}`
: key
if (isEntryPoint) {
entryPoint = key
}
const currentVariables = variables[variablesPath]
const expandObj: any = {
property: entityName,
}
const reference = isEntryPoint ? remoteJoinerQuery : expandObj
if (currentVariables) {
reference.args = Object.entries(currentVariables).map(
([name, value]) => ({
name,
value,
})
)
}
if (value.__args) {
reference.args = [
...(reference.__args || []),
...Object.entries(value.__args).map(([name, value]) => ({
name,
value,
})),
]
}
if (value.__directives) {
reference.directives = Object.entries(value.__directives).map(
([name, value]) => ({ name, value })
)
}
if (value.fields) {
reference.fields = value.fields
}
if (isEntryPoint) {
remoteJoinerQuery.alias = key
} else {
remoteJoinerQuery.expands!.push(expandObj)
}
extractRecursive(value, isEntryPoint ? "" : entityName, false)
} }
return remoteJoinerQuery return remoteJoinerQuery
@@ -53,7 +53,7 @@ export class RemoteJoiner {
}, {}) }, {})
if (expands) { if (expands) {
for (const key in expands) { for (const key of Object.keys(expands ?? {})) {
const expand = expands[key] const expand = expands[key]
if (expand) { if (expand) {
if (Array.isArray(data[key])) { if (Array.isArray(data[key])) {
@@ -146,12 +146,14 @@ export class RemoteJoiner {
const isReadOnlyDefinition = const isReadOnlyDefinition =
service.serviceName === undefined || service.isReadOnlyLink service.serviceName === undefined || service.isReadOnlyLink
if (!isReadOnlyDefinition) { if (!isReadOnlyDefinition) {
if (!service.alias) { service.alias ??= []
service.alias = [{ name: service.serviceName!.toLowerCase() }]
} else if (!Array.isArray(service.alias)) { if (!Array.isArray(service.alias)) {
service.alias = [service.alias] service.alias = [service.alias]
} }
service.alias.push({ name: service.serviceName! })
// handle alias.name as array // handle alias.name as array
for (let idx = 0; idx < service.alias.length; idx++) { for (let idx = 0; idx < service.alias.length; idx++) {
const alias = service.alias[idx] const alias = service.alias[idx]
@@ -173,6 +175,11 @@ export class RemoteJoiner {
for (const alias of service.alias) { for (const alias of service.alias) {
if (this.serviceConfigCache.has(`alias_${alias.name}}`)) { if (this.serviceConfigCache.has(`alias_${alias.name}}`)) {
const defined = this.serviceConfigCache.get(`alias_${alias.name}}`) const defined = this.serviceConfigCache.get(`alias_${alias.name}}`)
if (service.serviceName === defined?.serviceName) {
continue
}
throw new Error( throw new Error(
`Cannot add alias "${alias.name}" for "${service.serviceName}". It is already defined for Service "${defined?.serviceName}".` `Cannot add alias "${alias.name}" for "${service.serviceName}". It is already defined for Service "${defined?.serviceName}".`
) )
@@ -223,7 +230,9 @@ export class RemoteJoiner {
(rel) => rel.isInternalService === true (rel) => rel.isInternalService === true
) )
if (isInternalServicePresent) continue if (isInternalServicePresent) {
continue
}
throw new Error(`Service "${serviceName}" was not found`) throw new Error(`Service "${serviceName}" was not found`)
} }
@@ -338,7 +347,9 @@ export class RemoteJoiner {
relationship relationship
) )
const isObj = isDefined(response.path) const isObj = isDefined(response.path)
const resData = isObj ? response.data[response.path!] : response.data let resData = isObj ? response.data[response.path!] : response.data
resData = Array.isArray(resData) ? resData : [resData]
const filteredDataArray = resData.map((data: any) => const filteredDataArray = resData.map((data: any) =>
RemoteJoiner.filterFields(data, expand.fields, expand.expands) RemoteJoiner.filterFields(data, expand.fields, expand.expands)
@@ -1,5 +1,5 @@
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types" import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
import { createContainerLike, createMedusaContainer } from "@medusajs/utils" import { createMedusaContainer } from "@medusajs/utils"
import { asValue } from "awilix" import { asValue } from "awilix"
import { import {
@@ -25,7 +25,7 @@ export class GlobalWorkflow extends WorkflowManager {
if (!Array.isArray(modulesLoaded) && modulesLoaded) { if (!Array.isArray(modulesLoaded) && modulesLoaded) {
if (!("cradle" in modulesLoaded)) { if (!("cradle" in modulesLoaded)) {
container = createContainerLike(modulesLoaded) container = createMedusaContainer(modulesLoaded)
} else { } else {
container = modulesLoaded container = modulesLoaded
} }
@@ -1,5 +1,10 @@
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types" import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
import { createContainerLike, createMedusaContainer } from "@medusajs/utils" import {
MedusaContext,
MedusaContextType,
MedusaModuleType,
createMedusaContainer,
} from "@medusajs/utils"
import { asValue } from "awilix" import { asValue } from "awilix"
import { import {
DistributedTransaction, DistributedTransaction,
@@ -28,6 +33,7 @@ export class LocalWorkflow {
protected customOptions: Partial<TransactionModelOptions> = {} protected customOptions: Partial<TransactionModelOptions> = {}
protected workflow: WorkflowDefinition protected workflow: WorkflowDefinition
protected handlers: Map<string, StepHandler> protected handlers: Map<string, StepHandler>
protected medusaContext?: Context
constructor( constructor(
workflowId: string, workflowId: string,
@@ -47,9 +53,9 @@ export class LocalWorkflow {
if (!Array.isArray(modulesLoaded) && modulesLoaded) { if (!Array.isArray(modulesLoaded) && modulesLoaded) {
if (!("cradle" in modulesLoaded)) { if (!("cradle" in modulesLoaded)) {
container = createContainerLike(modulesLoaded) container = createMedusaContainer(modulesLoaded)
} else { } else {
container = modulesLoaded container = createMedusaContainer({}, modulesLoaded) // copy container
} }
} else if (Array.isArray(modulesLoaded) && modulesLoaded.length) { } else if (Array.isArray(modulesLoaded) && modulesLoaded.length) {
container = createMedusaContainer() container = createMedusaContainer()
@@ -60,7 +66,49 @@ export class LocalWorkflow {
} }
} }
this.container = container this.container = this.contextualizedMedusaModules(container)
}
private contextualizedMedusaModules(container) {
if (!container) {
return createMedusaContainer()
}
// eslint-disable-next-line
const this_ = this
const originalResolver = container.resolve
container.resolve = function (registrationName, opts) {
const resolved = originalResolver(registrationName, opts)
if (resolved?.constructor?.__type !== MedusaModuleType) {
return resolved
}
return new Proxy(resolved, {
get: function (target, prop) {
if (typeof target[prop] !== "function") {
return target[prop]
}
return async (...args) => {
const ctxIndex =
MedusaContext.getIndex(target, prop as string) ?? args.length - 1
const hasContext = args[ctxIndex]?.__type === MedusaContextType
if (!hasContext) {
const context = this_.medusaContext
if (context?.__type === MedusaContextType) {
delete context?.manager
delete context?.transactionManager
args[ctxIndex] = context
}
}
return await target[prop].apply(target, [...args])
}
},
})
}
return container
} }
protected commit() { protected commit() {
@@ -265,7 +313,7 @@ export class LocalWorkflow {
if (this.flow.hasChanges) { if (this.flow.hasChanges) {
this.commit() this.commit()
} }
this.medusaContext = context
const { handler, orchestrator } = this.workflow const { handler, orchestrator } = this.workflow
const transaction = await orchestrator.beginTransaction( const transaction = await orchestrator.beginTransaction(
@@ -288,6 +336,7 @@ export class LocalWorkflow {
} }
async getRunningTransaction(uniqueTransactionId: string, context?: Context) { async getRunningTransaction(uniqueTransactionId: string, context?: Context) {
this.medusaContext = context
const { handler, orchestrator } = this.workflow const { handler, orchestrator } = this.workflow
const transaction = await orchestrator.retrieveExistingTransaction( const transaction = await orchestrator.retrieveExistingTransaction(
@@ -303,6 +352,7 @@ export class LocalWorkflow {
context?: Context, context?: Context,
subscribe?: DistributedTransactionEvents subscribe?: DistributedTransactionEvents
) { ) {
this.medusaContext = context
const { orchestrator } = this.workflow const { orchestrator } = this.workflow
const transaction = await this.getRunningTransaction( const transaction = await this.getRunningTransaction(
@@ -329,6 +379,7 @@ export class LocalWorkflow {
context?: Context, context?: Context,
subscribe?: DistributedTransactionEvents subscribe?: DistributedTransactionEvents
): Promise<DistributedTransaction> { ): Promise<DistributedTransaction> {
this.medusaContext = context
const { handler, orchestrator } = this.workflow const { handler, orchestrator } = this.workflow
const { cleanUpEventListeners } = this.registerEventCallbacks({ const { cleanUpEventListeners } = this.registerEventCallbacks({
@@ -355,6 +406,7 @@ export class LocalWorkflow {
context?: Context, context?: Context,
subscribe?: DistributedTransactionEvents subscribe?: DistributedTransactionEvents
): Promise<DistributedTransaction> { ): Promise<DistributedTransaction> {
this.medusaContext = context
const { handler, orchestrator } = this.workflow const { handler, orchestrator } = this.workflow
const { cleanUpEventListeners } = this.registerEventCallbacks({ const { cleanUpEventListeners } = this.registerEventCallbacks({
@@ -81,7 +81,7 @@ export class WorkflowManager {
const finalFlow = flow instanceof OrchestratorBuilder ? flow.build() : flow const finalFlow = flow instanceof OrchestratorBuilder ? flow.build() : flow
if (WorkflowManager.workflows.has(workflowId)) { if (WorkflowManager.workflows.has(workflowId)) {
function excludeStepUuid(key, value) { const excludeStepUuid = (key, value) => {
return key === "uuid" ? undefined : value return key === "uuid" ? undefined : value
} }
@@ -7,7 +7,6 @@ import {
FindConfig, FindConfig,
InternalModuleDeclaration, InternalModuleDeclaration,
ModuleJoinerConfig, ModuleJoinerConfig,
MoneyAmountDTO,
PriceSetDTO, PriceSetDTO,
PricingContext, PricingContext,
PricingFilters, PricingFilters,
@@ -56,14 +55,14 @@ import {
PriceSetService, PriceSetService,
RuleTypeService, RuleTypeService,
} from "@services" } from "@services"
import { ServiceTypes } from "@types"
import { validatePriceListDates } from "@utils"
import { CreatePriceListRuleValueDTO } from "src/types/services"
import { import {
LinkableKeys, LinkableKeys,
entityNameToLinkableKeysMap, entityNameToLinkableKeysMap,
joinerConfig, joinerConfig,
} from "../joiner-config" } from "../joiner-config"
import { validatePriceListDates } from "@utils"
import { ServiceTypes } from "@types"
import { CreatePriceListRuleValueDTO } from "src/types/services"
type InjectedDependencies = { type InjectedDependencies = {
baseRepository: DAL.RepositoryService baseRepository: DAL.RepositoryService
pricingRepository: PricingRepositoryService pricingRepository: PricingRepositoryService
@@ -639,8 +638,8 @@ export default class PricingModuleService<
// Price set money amounts // Price set money amounts
let maCursor = 0 let maCursor = 0
const priceSetMoneyAmountsBulkData: unknown[] = const priceSetMoneyAmountsBulkData: unknown[] = input.flatMap(
input.flatMap(({ priceSetId, prices }) => ({ priceSetId, prices }) =>
prices.map(() => { prices.map(() => {
const ma = createdMoneyAmounts[maCursor] const ma = createdMoneyAmounts[maCursor]
const numberOfRules = Object.entries( const numberOfRules = Object.entries(
@@ -654,7 +653,7 @@ export default class PricingModuleService<
rules_count: numberOfRules, rules_count: numberOfRules,
} }
}) })
) )
const createdPriceSetMoneyAmounts = const createdPriceSetMoneyAmounts =
await this.priceSetMoneyAmountService_.create( await this.priceSetMoneyAmountService_.create(
priceSetMoneyAmountsBulkData as ServiceTypes.CreatePriceSetMoneyAmountDTO[], priceSetMoneyAmountsBulkData as ServiceTypes.CreatePriceSetMoneyAmountDTO[],
@@ -1638,7 +1637,6 @@ export default class PricingModuleService<
validatePriceListDates(updatePriceListData) validatePriceListDates(updatePriceListData)
if (typeof rules === "object") { if (typeof rules === "object") {
updatePriceListData.rules_count = Object.keys(rules).length updatePriceListData.rules_count = Object.keys(rules).length
} }
@@ -54,6 +54,11 @@ import {
MedusaError, MedusaError,
promiseAll, promiseAll,
} from "@medusajs/utils" } from "@medusajs/utils"
import { ProductEventData, ProductEvents } from "../types/services/product"
import {
ProductCategoryEventData,
ProductCategoryEvents,
} from "../types/services/product-category"
import { import {
CreateProductOptionValueDTO, CreateProductOptionValueDTO,
UpdateProductOptionValueDTO, UpdateProductOptionValueDTO,
@@ -63,11 +68,6 @@ import {
joinerConfig, joinerConfig,
LinkableKeys, LinkableKeys,
} from "./../joiner-config" } from "./../joiner-config"
import {
ProductCategoryEventData,
ProductCategoryEvents,
} from "../types/services/product-category"
import { ProductEventData, ProductEvents } from "../types/services/product"
type InjectedDependencies = { type InjectedDependencies = {
baseRepository: DAL.RepositoryService baseRepository: DAL.RepositoryService
@@ -897,7 +897,7 @@ export default class ProductModuleService<
@InjectManager("baseRepository_") @InjectManager("baseRepository_")
async create( async create(
data: ProductTypes.CreateProductDTO[], data: ProductTypes.CreateProductDTO[],
sharedContext?: Context @MedusaContext() sharedContext: Context = {}
): Promise<ProductTypes.ProductDTO[]> { ): Promise<ProductTypes.ProductDTO[]> {
const products = await this.create_(data, sharedContext) const products = await this.create_(data, sharedContext)
const createdProducts = await this.baseRepository_.serialize< const createdProducts = await this.baseRepository_.serialize<
@@ -1319,7 +1319,7 @@ export default class ProductModuleService<
>( >(
productIds: string[], productIds: string[],
{ returnLinkableKeys }: SoftDeleteReturn<TReturnableLinkableKeys> = {}, { returnLinkableKeys }: SoftDeleteReturn<TReturnableLinkableKeys> = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> { ): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
const [products, cascadedEntitiesMap] = await this.softDelete_( const [products, cascadedEntitiesMap] = await this.softDelete_(
productIds, productIds,
@@ -1369,7 +1369,7 @@ export default class ProductModuleService<
>( >(
productIds: string[], productIds: string[],
{ returnLinkableKeys }: RestoreReturn<TReturnableLinkableKeys> = {}, { returnLinkableKeys }: RestoreReturn<TReturnableLinkableKeys> = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> { ): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
const [_, cascadedEntitiesMap] = await this.restore_( const [_, cascadedEntitiesMap] = await this.restore_(
productIds, productIds,
@@ -939,7 +939,7 @@ export default class PromotionModuleService<
>( >(
ids: string | string[], ids: string | string[],
{ returnLinkableKeys }: SoftDeleteReturn<TReturnableLinkableKeys> = {}, { returnLinkableKeys }: SoftDeleteReturn<TReturnableLinkableKeys> = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> { ): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
const idsToDelete = Array.isArray(ids) ? ids : [ids] const idsToDelete = Array.isArray(ids) ? ids : [ids]
let [_, cascadedEntitiesMap] = await this.softDelete_( let [_, cascadedEntitiesMap] = await this.softDelete_(
@@ -975,7 +975,7 @@ export default class PromotionModuleService<
>( >(
ids: string | string[], ids: string | string[],
{ returnLinkableKeys }: RestoreReturn<TReturnableLinkableKeys> = {}, { returnLinkableKeys }: RestoreReturn<TReturnableLinkableKeys> = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> { ): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
const idsToRestore = Array.isArray(ids) ? ids : [ids] const idsToRestore = Array.isArray(ids) ? ids : [ids]
const [_, cascadedEntitiesMap] = await this.restore_( const [_, cascadedEntitiesMap] = await this.restore_(
@@ -1376,7 +1376,7 @@ export default class PromotionModuleService<
async softDeleteCampaigns<TReturnableLinkableKeys extends string>( async softDeleteCampaigns<TReturnableLinkableKeys extends string>(
ids: string | string[], ids: string | string[],
{ returnLinkableKeys }: SoftDeleteReturn<TReturnableLinkableKeys> = {}, { returnLinkableKeys }: SoftDeleteReturn<TReturnableLinkableKeys> = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> { ): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
const idsToDelete = Array.isArray(ids) ? ids : [ids] const idsToDelete = Array.isArray(ids) ? ids : [ids]
let [_, cascadedEntitiesMap] = await this.softDeleteCampaigns_( let [_, cascadedEntitiesMap] = await this.softDeleteCampaigns_(
@@ -1412,7 +1412,7 @@ export default class PromotionModuleService<
>( >(
ids: string | string[], ids: string | string[],
{ returnLinkableKeys }: RestoreReturn<TReturnableLinkableKeys> = {}, { returnLinkableKeys }: RestoreReturn<TReturnableLinkableKeys> = {},
sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> { ): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
const idsToRestore = Array.isArray(ids) ? ids : [ids] const idsToRestore = Array.isArray(ids) ? ids : [ids]
const [_, cascadedEntitiesMap] = await this.restoreCampaigns_( const [_, cascadedEntitiesMap] = await this.restoreCampaigns_(
@@ -64,7 +64,7 @@ export default class StockLocationService {
async list( async list(
selector: FilterableStockLocationProps = {}, selector: FilterableStockLocationProps = {},
config: FindConfig<StockLocation> = { relations: [], skip: 0, take: 10 }, config: FindConfig<StockLocation> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<StockLocation[]> { ): Promise<StockLocation[]> {
const [locations] = await this.listAndCount(selector, config, context) const [locations] = await this.listAndCount(selector, config, context)
return locations return locations
@@ -80,7 +80,7 @@ export default class StockLocationService {
async listAndCount( async listAndCount(
selector: FilterableStockLocationProps = {}, selector: FilterableStockLocationProps = {},
config: FindConfig<StockLocation> = { relations: [], skip: 0, take: 10 }, config: FindConfig<StockLocation> = { relations: [], skip: 0, take: 10 },
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<[StockLocation[], number]> { ): Promise<[StockLocation[], number]> {
const manager = context.transactionManager ?? this.manager_ const manager = context.transactionManager ?? this.manager_
const locationRepo = manager.getRepository(StockLocation) const locationRepo = manager.getRepository(StockLocation)
@@ -119,7 +119,7 @@ export default class StockLocationService {
async retrieve( async retrieve(
stockLocationId: string, stockLocationId: string,
config: FindConfig<StockLocation> = {}, config: FindConfig<StockLocation> = {},
context: SharedContext = {} @MedusaContext() context: SharedContext = {}
): Promise<StockLocation> { ): Promise<StockLocation> {
if (!isDefined(stockLocationId)) { if (!isDefined(stockLocationId)) {
throw new MedusaError( throw new MedusaError(
+6
View File
@@ -22,6 +22,7 @@ export type SharedContext = {
* A context used to share resources, such as transaction manager, between the application and the module. * A context used to share resources, such as transaction manager, between the application and the module.
*/ */
export type Context<TManager = unknown> = { export type Context<TManager = unknown> = {
__type?: "MedusaContext"
/** /**
* An instance of a transaction manager of type `TManager`, which is a typed parameter passed to the context to specify the type of the `transactionManager`. * An instance of a transaction manager of type `TManager`, which is a typed parameter passed to the context to specify the type of the `transactionManager`.
*/ */
@@ -42,4 +43,9 @@ export type Context<TManager = unknown> = {
* A string indicating the ID of the current transaction. * A string indicating the ID of the current transaction.
*/ */
transactionId?: string transactionId?: string
/**
* A string indicating the ID of the current request.
*/
requestId?: string
} }
@@ -18,8 +18,11 @@ import {
FilterQuery as MikroFilterQuery, FilterQuery as MikroFilterQuery,
} from "@mikro-orm/core/typings" } from "@mikro-orm/core/typings"
import { MedusaError, isString } from "../../common" import { MedusaError, isString } from "../../common"
import { MedusaContext } from "../../decorators" import {
import { InjectTransactionManager, buildQuery } from "../../modules-sdk" InjectTransactionManager,
MedusaContext,
buildQuery,
} from "../../modules-sdk"
import { import {
getSoftDeletedCascadedEntitiesIdsMappedBy, getSoftDeletedCascadedEntitiesIdsMappedBy,
transactionWrapper, transactionWrapper,
+1 -1
View File
@@ -1,5 +1,5 @@
import { Context, DAL, RepositoryTransformOptions } from "@medusajs/types" import { Context, DAL, RepositoryTransformOptions } from "@medusajs/types"
import { MedusaContext } from "../decorators" import { MedusaContext } from "../modules-sdk"
import { transactionWrapper } from "./utils" import { transactionWrapper } from "./utils"
class AbstractBase<T = any> { class AbstractBase<T = any> {
-1
View File
@@ -1,2 +1 @@
export * from "./context-parameter"
export * from "./inject-entity-manager" export * from "./inject-entity-manager"
@@ -1,5 +1,7 @@
import { Context, SharedContext } from "@medusajs/types" import { Context, SharedContext } from "@medusajs/types"
import { MedusaContextType } from "../modules-sdk/decorators"
// @deprecated Use InjectManager instead
export function InjectEntityManager( export function InjectEntityManager(
shouldForceTransaction: (target: any) => boolean = () => false, shouldForceTransaction: (target: any) => boolean = () => false,
managerProperty: string | false = "manager_" managerProperty: string | false = "manager_"
@@ -31,7 +33,7 @@ export function InjectEntityManager(
: this[managerProperty] : this[managerProperty]
).transaction( ).transaction(
async (transactionManager) => { async (transactionManager) => {
args[argIndex] = args[argIndex] ?? {} args[argIndex] = args[argIndex] ?? { __type: MedusaContextType }
args[argIndex].transactionManager = transactionManager args[argIndex].transactionManager = transactionManager
return await originalMethod.apply(this, args) return await originalMethod.apply(this, args)
+4 -2
View File
@@ -4,13 +4,15 @@ export * from "./common"
export * from "./dal" export * from "./dal"
export * from "./decorators" export * from "./decorators"
export * from "./event-bus" export * from "./event-bus"
export * from "./exceptions"
export * from "./feature-flags" export * from "./feature-flags"
export * from "./modules-sdk" export * from "./modules-sdk"
export * from "./orchestration"
export * from "./payment" export * from "./payment"
export * from "./pricing" export * from "./pricing"
export * from "./product" export * from "./product"
export * from "./promotion" export * from "./promotion"
export * from "./search" export * from "./search"
export * from "./shipping" export * from "./shipping"
export * from "./orchestration"
export * from "./exceptions" export const MedusaModuleType = Symbol.for("MedusaModule")
@@ -14,7 +14,7 @@ import {
shouldForceTransaction, shouldForceTransaction,
upperCaseFirst, upperCaseFirst,
} from "../common" } from "../common"
import { MedusaContext } from "../decorators" import { MedusaContext } from "../modules-sdk"
import { buildQuery } from "./build-query" import { buildQuery } from "./build-query"
import { InjectManager, InjectTransactionManager } from "./decorators" import { InjectManager, InjectTransactionManager } from "./decorators"
@@ -8,3 +8,12 @@ export function MedusaContext() {
target.MedusaContextIndex_[propertyKey] = parameterIndex target.MedusaContextIndex_[propertyKey] = parameterIndex
} }
} }
MedusaContext.getIndex = function (
target: any,
propertyKey: string
): number | undefined {
return target.MedusaContextIndex_?.[propertyKey]
}
export const MedusaContextType = "MedusaContext"
@@ -1,3 +1,4 @@
export * from "./context-parameter"
export * from "./inject-manager" export * from "./inject-manager"
export * from "./inject-shared-context" export * from "./inject-shared-context"
export * from "./inject-transaction-manager" export * from "./inject-transaction-manager"
@@ -1,4 +1,4 @@
import { Context, SharedContext } from "@medusajs/types" import { Context } from "@medusajs/types"
export function InjectManager(managerProperty?: string): MethodDecorator { export function InjectManager(managerProperty?: string): MethodDecorator {
return function ( return function (
@@ -16,13 +16,31 @@ export function InjectManager(managerProperty?: string): MethodDecorator {
const argIndex = target.MedusaContextIndex_[propertyKey] const argIndex = target.MedusaContextIndex_[propertyKey]
descriptor.value = function (...args: any[]) { descriptor.value = function (...args: any[]) {
const context: SharedContext | Context = { ...(args[argIndex] ?? {}) } const originalContext = args[argIndex] ?? {}
const copiedContext = {} as Context
for (const key in originalContext) {
if (key === "manager" || key === "transactionManager") {
continue
}
Object.defineProperty(copiedContext, key, {
get: function () {
return originalContext[key]
},
set: function (value) {
originalContext[key] = value
},
})
}
const resourceWithManager = !managerProperty const resourceWithManager = !managerProperty
? this ? this
: this[managerProperty] : this[managerProperty]
context.manager = context.manager ?? resourceWithManager.getFreshManager() copiedContext.manager ??= resourceWithManager.getFreshManager()
args[argIndex] = context copiedContext.transactionManager ??= originalContext?.transactionManager
args[argIndex] = copiedContext
return originalMethod.apply(this, args) return originalMethod.apply(this, args)
} }
@@ -1,4 +1,5 @@
import { Context, SharedContext } from "@medusajs/types" import { Context, SharedContext } from "@medusajs/types"
import { MedusaContextType } from "./context-parameter"
export function InjectSharedContext(): MethodDecorator { export function InjectSharedContext(): MethodDecorator {
return function ( return function (
@@ -16,7 +17,9 @@ export function InjectSharedContext(): MethodDecorator {
const argIndex = target.MedusaContextIndex_[propertyKey] const argIndex = target.MedusaContextIndex_[propertyKey]
descriptor.value = function (...args: any[]) { descriptor.value = function (...args: any[]) {
const context: SharedContext | Context = { ...(args[argIndex] ?? {}) } const context: SharedContext | Context = {
...(args[argIndex] ?? { __type: MedusaContextType }),
}
args[argIndex] = context args[argIndex] = context
return originalMethod.apply(this, args) return originalMethod.apply(this, args)
@@ -1,5 +1,6 @@
import { Context, SharedContext } from "@medusajs/types" import { Context } from "@medusajs/types"
import { isString } from "../../common" import { isString } from "../../common"
import { MedusaContextType } from "./context-parameter"
export function InjectTransactionManager( export function InjectTransactionManager(
shouldForceTransactionOrManagerProperty: shouldForceTransactionOrManagerProperty:
@@ -31,7 +32,8 @@ export function InjectTransactionManager(
const argIndex = target.MedusaContextIndex_[propertyKey] const argIndex = target.MedusaContextIndex_[propertyKey]
descriptor.value = async function (...args: any[]) { descriptor.value = async function (...args: any[]) {
const shouldForceTransactionRes = shouldForceTransaction(target) const shouldForceTransactionRes = shouldForceTransaction(target)
const context: SharedContext | Context = args[argIndex] ?? {} const context: Context = args[argIndex] ?? {}
const originalContext = args[argIndex] ?? {}
if (!shouldForceTransactionRes && context?.transactionManager) { if (!shouldForceTransactionRes && context?.transactionManager) {
return await originalMethod.apply(this, args) return await originalMethod.apply(this, args)
@@ -42,8 +44,27 @@ export function InjectTransactionManager(
: this[managerProperty] : this[managerProperty]
).transaction( ).transaction(
async (transactionManager) => { async (transactionManager) => {
args[argIndex] = { ...(args[argIndex] ?? {}) } const copiedContext = {} as Context
args[argIndex].transactionManager = transactionManager for (const key in originalContext) {
if (key === "manager" || key === "transactionManager") {
continue
}
Object.defineProperty(copiedContext, key, {
get: function () {
return originalContext[key]
},
set: function (value) {
originalContext[key] = value
},
})
}
copiedContext.transactionManager ??= transactionManager
copiedContext.manager ??= originalContext?.manager
copiedContext.__type ??= MedusaContextType
args[argIndex] = copiedContext
return await originalMethod.apply(this, args) return await originalMethod.apply(this, args)
}, },
@@ -9,6 +9,7 @@ import {
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types" import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
import { MedusaModule } from "@medusajs/modules-sdk" import { MedusaModule } from "@medusajs/modules-sdk"
import { MedusaContextType } from "@medusajs/utils"
import { EOL } from "os" import { EOL } from "os"
import { ulid } from "ulid" import { ulid } from "ulid"
import { MedusaWorkflow } from "../medusa-workflow" import { MedusaWorkflow } from "../medusa-workflow"
@@ -159,7 +160,7 @@ function createContextualWorkflowRunner<
) )
} }
const flow = new LocalWorkflow(workflowId, container) const flow = new LocalWorkflow(workflowId, container!)
const originalRun = flow.run.bind(flow) const originalRun = flow.run.bind(flow)
const originalRegisterStepSuccess = flow.registerStepSuccess.bind(flow) const originalRegisterStepSuccess = flow.registerStepSuccess.bind(flow)
@@ -197,7 +198,13 @@ function createContextualWorkflowRunner<
} }
const newRun = async ( const newRun = async (
{ input, context, throwOnError, resultFrom, events }: FlowRunOptions = { {
input,
context: outerContext,
throwOnError,
resultFrom,
events,
}: FlowRunOptions = {
throwOnError: true, throwOnError: true,
resultFrom: defaultResult, resultFrom: defaultResult,
} }
@@ -205,6 +212,13 @@ function createContextualWorkflowRunner<
resultFrom ??= defaultResult resultFrom ??= defaultResult
throwOnError ??= true throwOnError ??= true
const context = {
...outerContext,
__type: MedusaContextType,
}
context.transactionId ??= ulid()
if (typeof dataPreparation === "function") { if (typeof dataPreparation === "function") {
try { try {
const copyInput = input ? JSON.parse(JSON.stringify(input)) : input const copyInput = input ? JSON.parse(JSON.stringify(input)) : input
@@ -224,7 +238,7 @@ function createContextualWorkflowRunner<
return await originalExecution( return await originalExecution(
originalRun, originalRun,
{ throwOnError, resultFrom }, { throwOnError, resultFrom },
context?.transactionId ?? ulid(), context.transactionId,
input, input,
context, context,
events events
@@ -236,7 +250,7 @@ function createContextualWorkflowRunner<
{ {
response, response,
idempotencyKey, idempotencyKey,
context, context: outerContext,
throwOnError, throwOnError,
resultFrom, resultFrom,
events, events,
@@ -249,6 +263,13 @@ function createContextualWorkflowRunner<
resultFrom ??= defaultResult resultFrom ??= defaultResult
throwOnError ??= true throwOnError ??= true
const [, transactionId] = idempotencyKey.split(":")
const context = {
...outerContext,
transactionId,
__type: MedusaContextType,
}
return await originalExecution( return await originalExecution(
originalRegisterStepSuccess, originalRegisterStepSuccess,
{ throwOnError, resultFrom }, { throwOnError, resultFrom },
@@ -264,7 +285,7 @@ function createContextualWorkflowRunner<
{ {
response, response,
idempotencyKey, idempotencyKey,
context, context: outerContext,
throwOnError, throwOnError,
resultFrom, resultFrom,
events, events,
@@ -277,6 +298,13 @@ function createContextualWorkflowRunner<
resultFrom ??= defaultResult resultFrom ??= defaultResult
throwOnError ??= true throwOnError ??= true
const [, transactionId] = idempotencyKey.split(":")
const context = {
...outerContext,
transactionId,
__type: MedusaContextType,
}
return await originalExecution( return await originalExecution(
originalRegisterStepFailure, originalRegisterStepFailure,
{ throwOnError, resultFrom }, { throwOnError, resultFrom },