feat: event aggregator (#6218)
What: - Event Aggregator Util - Preparation for normalizing event in a new format (backward compatible with the current format) - GQL Schema to joiner config and some Entities configured - Link modules emmiting events
This commit is contained in:
1 parent
73fd92a1af
commit
884428a1b5
66 files changed
+1407
-749
No files matched your search
@@ -0,0 +1,13 @@
|
||||
---
|
||||
"@medusajs/event-bus-local": patch
|
||||
"@medusajs/event-bus-redis": patch
|
||||
"@medusajs/link-modules": patch
|
||||
"@medusajs/modules-sdk": patch
|
||||
"@medusajs/inventory": patch
|
||||
"@medusajs/product": patch
|
||||
"@medusajs/medusa": patch
|
||||
"@medusajs/types": patch
|
||||
"@medusajs/utils": patch
|
||||
---
|
||||
|
||||
Feat: Event Aggregator
|
||||
@@ -1,11 +1,11 @@
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { MikroOrmWrapper } from "../../../utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { createAuthProviders } from "../../../__fixtures__/auth-provider"
|
||||
import { createAuthUsers } from "../../../__fixtures__/auth-user"
|
||||
import { MikroOrmWrapper } from "../../../utils"
|
||||
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { MikroOrmWrapper } from "../../../utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { createAuthProviders } from "../../../__fixtures__/auth-provider"
|
||||
import { createAuthUsers } from "../../../__fixtures__/auth-user"
|
||||
import { MikroOrmWrapper } from "../../../utils"
|
||||
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { MikroOrmWrapper } from "../../../utils"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { createAuthProviders } from "../../../__fixtures__/auth-provider"
|
||||
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { createAuthProviders } from "../../../__fixtures__/auth-provider"
|
||||
import { MikroOrmWrapper } from "../../../utils"
|
||||
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
@@ -70,7 +69,10 @@ describe("AuthModuleService - AuthProvider", () => {
|
||||
},
|
||||
])
|
||||
|
||||
const { success, error } = await service.authenticate("notRegistered", {} as any)
|
||||
const { success, error } = await service.authenticate(
|
||||
"notRegistered",
|
||||
{} as any
|
||||
)
|
||||
|
||||
expect(success).toBe(false)
|
||||
expect(error).toEqual(
|
||||
@@ -79,12 +81,9 @@ describe("AuthModuleService - AuthProvider", () => {
|
||||
})
|
||||
|
||||
it("fails to authenticate using a valid provider with an invalid scope", async () => {
|
||||
const { success, error } = await service.authenticate(
|
||||
"emailpass",
|
||||
{
|
||||
authScope: "non-existing",
|
||||
} as any
|
||||
)
|
||||
const { success, error } = await service.authenticate("emailpass", {
|
||||
authScope: "non-existing",
|
||||
} as any)
|
||||
|
||||
expect(success).toBe(false)
|
||||
expect(error).toEqual(
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { MedusaContainer } from "@medusajs/modules-sdk"
|
||||
import { EmitData, EventBusTypes, Logger, Subscriber } from "@medusajs/types"
|
||||
import {
|
||||
EmitData,
|
||||
EventBusTypes,
|
||||
Logger,
|
||||
Message,
|
||||
Subscriber,
|
||||
} from "@medusajs/types"
|
||||
import { AbstractEventBusModuleService } from "@medusajs/utils"
|
||||
import { EventEmitter } from "events"
|
||||
import { ulid } from "ulid"
|
||||
@@ -37,14 +43,16 @@ export default class LocalEventBusService extends AbstractEventBusModuleService
|
||||
*/
|
||||
async emit<T>(data: EmitData<T>[]): Promise<void>
|
||||
|
||||
async emit<T, TInput extends string | EmitData<T>[] = string>(
|
||||
async emit<T>(data: Message<T>[]): Promise<void>
|
||||
|
||||
async emit<T, TInput extends string | EmitData<T>[] | Message<T>[] = string>(
|
||||
eventOrData: TInput,
|
||||
data?: T,
|
||||
options: Record<string, unknown> = {}
|
||||
): Promise<void> {
|
||||
const isBulkEmit = Array.isArray(eventOrData)
|
||||
|
||||
const events: EmitData[] = isBulkEmit
|
||||
const events: EmitData[] | Message<T>[] = isBulkEmit
|
||||
? eventOrData
|
||||
: [{ eventName: eventOrData, data }]
|
||||
|
||||
@@ -61,7 +69,8 @@ export default class LocalEventBusService extends AbstractEventBusModuleService
|
||||
continue
|
||||
}
|
||||
|
||||
this.eventEmitter_.emit(event.eventName, event.data)
|
||||
const data = (event as EmitData).data ?? (event as Message<T>).body
|
||||
this.eventEmitter_.emit(event.eventName, data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { InternalModuleDeclaration } from "@medusajs/modules-sdk"
|
||||
import { EmitData, Logger } from "@medusajs/types"
|
||||
import { AbstractEventBusModuleService } from "@medusajs/utils"
|
||||
import { EmitData, Logger, Message } from "@medusajs/types"
|
||||
import { AbstractEventBusModuleService, isString } from "@medusajs/utils"
|
||||
import { BulkJobOptions, JobsOptions, Queue, Worker } from "bullmq"
|
||||
import { Redis } from "ioredis"
|
||||
import { BullJob, EmitOptions, EventBusRedisModuleOptions } from "../types"
|
||||
@@ -67,7 +67,9 @@ export default class RedisEventBusService extends AbstractEventBusModuleService
|
||||
*/
|
||||
async emit<T>(data: EmitData<T>[]): Promise<void>
|
||||
|
||||
async emit<T, TInput extends string | EmitData<T>[] = string>(
|
||||
async emit<T>(data: Message<T>[]): Promise<void>
|
||||
|
||||
async emit<T, TInput extends string | EmitData<T>[] | Message<T>[] = string>(
|
||||
eventNameOrData: TInput,
|
||||
data?: T,
|
||||
options: BulkJobOptions | JobsOptions = {}
|
||||
@@ -84,10 +86,17 @@ export default class RedisEventBusService extends AbstractEventBusModuleService
|
||||
...globalJobOptions,
|
||||
} as EmitOptions
|
||||
|
||||
const dataBody = isString(eventNameOrData)
|
||||
? data ?? (data as Message<T>).body
|
||||
: undefined
|
||||
|
||||
const events = isBulkEmit
|
||||
? eventNameOrData.map((event) => ({
|
||||
name: event.eventName,
|
||||
data: { eventName: event.eventName, data: event.data },
|
||||
data: {
|
||||
eventName: event.eventName,
|
||||
data: (event as EmitData).data ?? (event as Message<T>).body,
|
||||
},
|
||||
opts: {
|
||||
...opts,
|
||||
// local options
|
||||
@@ -97,7 +106,7 @@ export default class RedisEventBusService extends AbstractEventBusModuleService
|
||||
: [
|
||||
{
|
||||
name: eventNameOrData as string,
|
||||
data: { eventName: eventNameOrData, data },
|
||||
data: { eventName: eventNameOrData, data: dataBody },
|
||||
opts: {
|
||||
...opts,
|
||||
// local options
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { InventoryItem, InventoryLevel, ReservationItem } from "./models"
|
||||
import moduleSchema from "./schema"
|
||||
|
||||
export const joinerConfig: ModuleJoinerConfig = {
|
||||
serviceName: Modules.INVENTORY,
|
||||
@@ -10,46 +11,30 @@ export const joinerConfig: ModuleJoinerConfig = {
|
||||
inventory_level_id: InventoryLevel.name,
|
||||
reservation_item_id: ReservationItem.name,
|
||||
},
|
||||
schema: moduleSchema,
|
||||
alias: [
|
||||
{
|
||||
name: "inventory_items",
|
||||
},
|
||||
{
|
||||
name: "inventory",
|
||||
},
|
||||
{
|
||||
name: "inventory_level",
|
||||
name: ["inventory_items", "inventory"],
|
||||
args: {
|
||||
entity: "InventoryItem",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["inventory_level", "inventory_levels"],
|
||||
args: {
|
||||
entity: "InventoryLevel",
|
||||
methodSuffix: "InventoryLevels",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inventory_levels",
|
||||
args: {
|
||||
methodSuffix: "InventoryLevels",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reservation_items",
|
||||
args: {
|
||||
methodSuffix: "ReservationItems",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reservation_item",
|
||||
args: {
|
||||
methodSuffix: "ReservationItems",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reservation",
|
||||
args: {
|
||||
methodSuffix: "ReservationItems",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reservations",
|
||||
name: [
|
||||
"reservation",
|
||||
"reservations",
|
||||
"reservation_item",
|
||||
"reservation_items",
|
||||
],
|
||||
args: {
|
||||
entity: "ReservationItem",
|
||||
methodSuffix: "ReservationItems",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
OneToMany,
|
||||
PrimaryColumn,
|
||||
UpdateDateColumn,
|
||||
} from "typeorm"
|
||||
import { InventoryLevel } from "./inventory-level"
|
||||
|
||||
@Entity()
|
||||
export class InventoryItem {
|
||||
@@ -67,6 +69,12 @@ export class InventoryItem {
|
||||
@Column({ type: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null
|
||||
|
||||
@OneToMany(
|
||||
() => InventoryLevel,
|
||||
(inventoryLevel) => inventoryLevel.inventory_item
|
||||
)
|
||||
inventory_levels!: InventoryLevel[]
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert(): void {
|
||||
this.id = generateEntityId(this.id, "iitem")
|
||||
|
||||
@@ -6,9 +6,12 @@ import {
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
UpdateDateColumn,
|
||||
} from "typeorm"
|
||||
import { InventoryItem } from "./inventory-item"
|
||||
|
||||
@Entity()
|
||||
@Index(["inventory_item_id", "location_id"], { unique: true })
|
||||
@@ -45,6 +48,10 @@ export class InventoryLevel {
|
||||
@Column({ type: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null
|
||||
|
||||
@ManyToOne(() => InventoryItem)
|
||||
@JoinColumn({ name: "inventory_item_id" })
|
||||
inventory_item: InventoryItem
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert(): void {
|
||||
this.id = generateEntityId(this.id, "ilev")
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
export default `
|
||||
scalar DateTime
|
||||
scalar JSON
|
||||
|
||||
type InventoryItem {
|
||||
id: ID!
|
||||
created_at: DateTime!
|
||||
updated_at: DateTime!
|
||||
deleted_at: DateTime
|
||||
sku: String
|
||||
origin_country: String
|
||||
hs_code: String
|
||||
mid_code: String
|
||||
material: String
|
||||
weight: Int
|
||||
length: Int
|
||||
height: Int
|
||||
width: Int
|
||||
requires_shipping: Boolean!
|
||||
description: String
|
||||
title: String
|
||||
thumbnail: String
|
||||
metadata: JSON
|
||||
|
||||
inventory_levels: [InventoryLevel]
|
||||
}
|
||||
|
||||
type InventoryLevel {
|
||||
id: ID!
|
||||
created_at: DateTime!
|
||||
updated_at: DateTime!
|
||||
deleted_at: DateTime
|
||||
inventory_item_id: String!
|
||||
location_id: String!
|
||||
stocked_quantity: Int!
|
||||
reserved_quantity: Int!
|
||||
incoming_quantity: Int!
|
||||
metadata: JSON
|
||||
}
|
||||
|
||||
type ReservationItem {
|
||||
id: ID!
|
||||
created_at: DateTime!
|
||||
updated_at: DateTime!
|
||||
deleted_at: DateTime
|
||||
line_item_id: String
|
||||
inventory_item_id: String!
|
||||
location_id: String!
|
||||
quantity: Int!
|
||||
external_id: String
|
||||
description: String
|
||||
created_by: String
|
||||
metadata: JSON
|
||||
}
|
||||
`
|
||||
@@ -12,6 +12,9 @@ export const ProductShippingProfile: ModuleJoinerConfig = {
|
||||
alias: [
|
||||
{
|
||||
name: "product_shipping_profile",
|
||||
args: {
|
||||
entity: "LinkProductShippingProfile",
|
||||
},
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "product_id", "profile_id"],
|
||||
|
||||
@@ -17,10 +17,13 @@ export const ProductVariantInventoryItem: ModuleJoinerConfig = {
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: "product_variant_inventory_item",
|
||||
},
|
||||
{
|
||||
name: "product_variant_inventory_items",
|
||||
name: [
|
||||
"product_variant_inventory_item",
|
||||
"product_variant_inventory_items",
|
||||
],
|
||||
args: {
|
||||
entity: "LinkProductVariantInventoryItem",
|
||||
},
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "variant_id", "inventory_item_id"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LINKS } from "../links"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { LINKS } from "../links"
|
||||
|
||||
export const ProductVariantPriceSet: ModuleJoinerConfig = {
|
||||
serviceName: LINKS.ProductVariantPriceSet,
|
||||
@@ -11,10 +11,10 @@ export const ProductVariantPriceSet: ModuleJoinerConfig = {
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: "product_variant_price_set",
|
||||
},
|
||||
{
|
||||
name: "product_variant_price_sets",
|
||||
name: ["product_variant_price_set", "product_variant_price_sets"],
|
||||
args: {
|
||||
entity: "LinkProductVariantPriceSet",
|
||||
},
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "variant_id", "price_set_id"],
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { InternalModuleDeclaration, MedusaModule } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
InternalModuleDeclaration,
|
||||
MedusaModule,
|
||||
ModuleRegistrationName,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
ILinkModule,
|
||||
@@ -15,11 +19,16 @@ import {
|
||||
ContainerRegistrationKeys,
|
||||
lowerCaseFirst,
|
||||
simpleHash,
|
||||
toPascalCase,
|
||||
} from "@medusajs/utils"
|
||||
import * as linkDefinitions from "../definitions"
|
||||
import { getMigration } from "../migration"
|
||||
import { InitializeModuleInjectableDependencies } from "../types"
|
||||
import { composeLinkName, generateGraphQLSchema } from "../utils"
|
||||
import {
|
||||
composeLinkName,
|
||||
composeTableName,
|
||||
generateGraphQLSchema,
|
||||
} from "../utils"
|
||||
import { getLinkModuleDefinition } from "./module-definition"
|
||||
|
||||
export const initialize = async (
|
||||
@@ -98,7 +107,28 @@ export const initialize = async (
|
||||
continue
|
||||
}
|
||||
|
||||
definition.schema = generateGraphQLSchema(definition, primary, foreign)
|
||||
const logger =
|
||||
injectedDependencies?.[ContainerRegistrationKeys.LOGGER] ?? console.log
|
||||
|
||||
definition.schema = generateGraphQLSchema(definition, primary, foreign, {
|
||||
logger,
|
||||
})
|
||||
|
||||
definition.alias ??= []
|
||||
for (const alias of definition.alias) {
|
||||
alias.args ??= {}
|
||||
|
||||
alias.args.entity = toPascalCase(
|
||||
"Link_" +
|
||||
(definition.databaseConfig?.tableName ??
|
||||
composeTableName(
|
||||
primary.serviceName,
|
||||
primary.foreignKey,
|
||||
foreign.serviceName,
|
||||
foreign.foreignKey
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
const moduleDefinition = getLinkModuleDefinition(
|
||||
definition,
|
||||
@@ -110,6 +140,7 @@ export const initialize = async (
|
||||
key: serviceKey,
|
||||
registrationName: serviceKey,
|
||||
label: serviceKey,
|
||||
dependencies: [ModuleRegistrationName.EVENT_BUS],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: injectedDependencies?.[
|
||||
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
ModuleJoinerConfig,
|
||||
ModulesSdkTypes,
|
||||
} from "@medusajs/types"
|
||||
import { lowerCaseFirst, simpleHash, toPascalCase } from "@medusajs/utils"
|
||||
import { asClass, asValue } from "awilix"
|
||||
import { composeLinkName, composeTableName } from "../utils"
|
||||
|
||||
export function containerLoader(entity, joinerConfig: ModuleJoinerConfig) {
|
||||
return async (
|
||||
@@ -22,6 +24,29 @@ export function containerLoader(entity, joinerConfig: ModuleJoinerConfig) {
|
||||
): Promise<void> => {
|
||||
const [primary, foreign] = joinerConfig.relationships!
|
||||
|
||||
const serviceName = !joinerConfig.isReadOnlyLink
|
||||
? lowerCaseFirst(
|
||||
joinerConfig.serviceName ??
|
||||
composeLinkName(
|
||||
primary.serviceName,
|
||||
primary.foreignKey,
|
||||
foreign.serviceName,
|
||||
foreign.foreignKey
|
||||
)
|
||||
)
|
||||
: simpleHash(JSON.stringify(joinerConfig.extends))
|
||||
|
||||
const entityName = toPascalCase(
|
||||
"Link_" +
|
||||
(joinerConfig.databaseConfig?.tableName ??
|
||||
composeTableName(
|
||||
primary.serviceName,
|
||||
primary.foreignKey,
|
||||
foreign.serviceName,
|
||||
foreign.foreignKey
|
||||
))
|
||||
)
|
||||
|
||||
container.register({
|
||||
joinerConfig: asValue(joinerConfig),
|
||||
primaryKey: asValue(primary.foreignKey.split(",")),
|
||||
@@ -35,6 +60,8 @@ export function containerLoader(entity, joinerConfig: ModuleJoinerConfig) {
|
||||
|
||||
baseRepository: asClass(BaseRepository).singleton(),
|
||||
linkRepository: asClass(getLinkRepository(entity)).singleton(),
|
||||
entityName: asValue(entityName),
|
||||
serviceName: asValue(serviceName),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
Context,
|
||||
DAL,
|
||||
FindConfig,
|
||||
IEventBusModuleService,
|
||||
ILinkModule,
|
||||
InternalModuleDeclaration,
|
||||
ModuleJoinerConfig,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
SoftDeleteReturn,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
CommonEvents,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MapToConfig,
|
||||
@@ -24,14 +26,20 @@ import { shouldForceTransaction } from "../utils"
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
linkService: LinkService<any>
|
||||
eventBusModuleService?: IEventBusModuleService
|
||||
primaryKey: string | string[]
|
||||
foreignKey: string
|
||||
extraFields: string[]
|
||||
entityName: string
|
||||
serviceName: string
|
||||
}
|
||||
|
||||
export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
protected readonly linkService_: LinkService<TLink>
|
||||
protected readonly eventBusModuleService_?: IEventBusModuleService
|
||||
protected readonly entityName_: string
|
||||
protected readonly serviceName_: string
|
||||
protected primaryKey_: string[]
|
||||
protected foreignKey_: string
|
||||
protected extraFields_: string[]
|
||||
@@ -40,17 +48,23 @@ export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
{
|
||||
baseRepository,
|
||||
linkService,
|
||||
eventBusModuleService,
|
||||
primaryKey,
|
||||
foreignKey,
|
||||
extraFields,
|
||||
entityName,
|
||||
serviceName,
|
||||
}: InjectedDependencies,
|
||||
readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
this.baseRepository_ = baseRepository
|
||||
this.linkService_ = linkService
|
||||
this.eventBusModuleService_ = eventBusModuleService
|
||||
this.primaryKey_ = !Array.isArray(primaryKey) ? [primaryKey] : primaryKey
|
||||
this.foreignKey_ = foreignKey
|
||||
this.extraFields_ = extraFields
|
||||
this.entityName_ = entityName
|
||||
this.serviceName_ = serviceName
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
@@ -188,6 +202,21 @@ export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
|
||||
const links = await this.linkService_.create(data, sharedContext)
|
||||
|
||||
await this.eventBusModuleService_?.emit<Record<string, unknown>>(
|
||||
(data as { id: unknown }[]).map(({ id }) => ({
|
||||
eventName: this.entityName_ + "." + CommonEvents.ATTACHED,
|
||||
body: {
|
||||
metadata: {
|
||||
service: this.serviceName_,
|
||||
action: CommonEvents.ATTACHED,
|
||||
object: this.entityName_,
|
||||
eventGroupId: sharedContext.eventGroupId,
|
||||
},
|
||||
data: { id },
|
||||
},
|
||||
}))
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<object[]>(links)
|
||||
}
|
||||
|
||||
@@ -224,6 +253,22 @@ export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
this.validateFields(data)
|
||||
|
||||
await this.linkService_.delete(data, sharedContext)
|
||||
|
||||
const allData = Array.isArray(data) ? data : [data]
|
||||
await this.eventBusModuleService_?.emit<Record<string, unknown>>(
|
||||
allData.map(({ id }) => ({
|
||||
eventName: this.entityName_ + "." + CommonEvents.DETACHED,
|
||||
body: {
|
||||
metadata: {
|
||||
service: this.serviceName_,
|
||||
action: CommonEvents.DETACHED,
|
||||
object: this.entityName_,
|
||||
eventGroupId: sharedContext.eventGroupId,
|
||||
},
|
||||
data: { id },
|
||||
},
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
async softDelete(
|
||||
@@ -233,7 +278,10 @@ export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
): Promise<Record<string, unknown[]> | void> {
|
||||
this.validateFields(data)
|
||||
|
||||
let [, cascadedEntitiesMap] = await this.softDelete_(data, sharedContext)
|
||||
let [deletedEntities, cascadedEntitiesMap] = await this.softDelete_(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const pk = this.primaryKey_.join(",")
|
||||
const entityNameToLinkableKeysMap: MapToConfig = {
|
||||
@@ -256,6 +304,21 @@ export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
)
|
||||
}
|
||||
|
||||
await this.eventBusModuleService_?.emit<Record<string, unknown>>(
|
||||
(deletedEntities as { id: string }[]).map(({ id }) => ({
|
||||
eventName: this.entityName_ + "." + CommonEvents.DETACHED,
|
||||
body: {
|
||||
metadata: {
|
||||
service: this.serviceName_,
|
||||
action: CommonEvents.DETACHED,
|
||||
object: this.entityName_,
|
||||
eventGroupId: sharedContext.eventGroupId,
|
||||
},
|
||||
data: { id },
|
||||
},
|
||||
}))
|
||||
)
|
||||
|
||||
return mappedCascadedEntitiesMap ? mappedCascadedEntitiesMap : void 0
|
||||
}
|
||||
|
||||
@@ -263,7 +326,7 @@ export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
protected async softDelete_(
|
||||
data: any,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[string[], Record<string, string[]>]> {
|
||||
): Promise<[object[], Record<string, string[]>]> {
|
||||
return await this.linkService_.softDelete(data, sharedContext)
|
||||
}
|
||||
|
||||
@@ -274,7 +337,10 @@ export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
): Promise<Record<string, unknown[]> | void> {
|
||||
this.validateFields(data)
|
||||
|
||||
let [, cascadedEntitiesMap] = await this.restore_(data, sharedContext)
|
||||
let [restoredEntities, cascadedEntitiesMap] = await this.restore_(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const pk = this.primaryKey_.join(",")
|
||||
const entityNameToLinkableKeysMap: MapToConfig = {
|
||||
@@ -297,6 +363,21 @@ export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
)
|
||||
}
|
||||
|
||||
await this.eventBusModuleService_?.emit<Record<string, unknown>>(
|
||||
(restoredEntities as { id: string }[]).map(({ id }) => ({
|
||||
eventName: this.entityName_ + "." + CommonEvents.ATTACHED,
|
||||
body: {
|
||||
metadata: {
|
||||
service: this.serviceName_,
|
||||
action: CommonEvents.ATTACHED,
|
||||
object: this.entityName_,
|
||||
eventGroupId: sharedContext.eventGroupId,
|
||||
},
|
||||
data: { id },
|
||||
},
|
||||
}))
|
||||
)
|
||||
|
||||
return mappedCascadedEntitiesMap ? mappedCascadedEntitiesMap : void 0
|
||||
}
|
||||
|
||||
@@ -304,7 +385,7 @@ export default class LinkModuleService<TLink> implements ILinkModule {
|
||||
async restore_(
|
||||
data: any,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[string[], Record<string, string[]>]> {
|
||||
): Promise<[object[], Record<string, string[]>]> {
|
||||
return await this.linkService_.restore(data, sharedContext)
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export default class LinkService<TEntity> {
|
||||
async softDelete(
|
||||
data: any,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[string[], Record<string, string[]>]> {
|
||||
): Promise<[object[], Record<string, string[]>]> {
|
||||
const filter = {}
|
||||
for (const key in data) {
|
||||
filter[key] = { $in: Array.isArray(data[key]) ? data[key] : [data[key]] }
|
||||
@@ -104,7 +104,7 @@ export default class LinkService<TEntity> {
|
||||
async restore(
|
||||
data: any,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[string[], Record<string, string[]>]> {
|
||||
): Promise<[object[], Record<string, string[]>]> {
|
||||
const filter = {}
|
||||
for (const key in data) {
|
||||
filter[key] = { $in: Array.isArray(data[key]) ? data[key] : [data[key]] }
|
||||
|
||||
@@ -16,8 +16,15 @@ class EventBus extends EventBusUtils.AbstractEventBusModuleService {
|
||||
options: Record<string, unknown>
|
||||
): Promise<void>
|
||||
async emit<T>(data: EventBusTypes.EmitData<T>[]): Promise<void>
|
||||
async emit<T>(data: EventBusTypes.Message<T>[]): Promise<void>
|
||||
|
||||
async emit<T, TInput extends string | EventBusTypes.EmitData<T>[] = string>(
|
||||
async emit<
|
||||
T,
|
||||
TInput extends
|
||||
| string
|
||||
| EventBusTypes.EmitData<T>[]
|
||||
| EventBusTypes.Message<T>[] = string
|
||||
>(
|
||||
eventOrData: TInput,
|
||||
data?: T,
|
||||
options: Record<string, unknown> = {}
|
||||
|
||||
@@ -20,7 +20,9 @@ export default {
|
||||
alias: [
|
||||
{
|
||||
name: ["publishable_api_key", "publishable_api_keys"],
|
||||
args: { entity: "PublishableApiKey" },
|
||||
args: {
|
||||
entity: "PublishableApiKey",
|
||||
},
|
||||
},
|
||||
],
|
||||
} as ModuleJoinerConfig
|
||||
@@ -5,25 +5,25 @@ export default {
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: { profile_id: "ShippingProfile" },
|
||||
schema: `
|
||||
scalar Date
|
||||
scalar JSON
|
||||
|
||||
type ShippingProfile {
|
||||
id: ID!
|
||||
name: String!
|
||||
type: String!
|
||||
created_at: Date!
|
||||
updated_at: Date!
|
||||
deleted_at: Date
|
||||
metadata: JSON
|
||||
}
|
||||
`,
|
||||
scalar Date
|
||||
scalar JSON
|
||||
|
||||
type ShippingProfile {
|
||||
id: ID!
|
||||
name: String!
|
||||
type: String!
|
||||
created_at: Date!
|
||||
updated_at: Date!
|
||||
deleted_at: Date
|
||||
metadata: JSON
|
||||
}
|
||||
`,
|
||||
alias: [
|
||||
{
|
||||
name: "shipping_profile",
|
||||
},
|
||||
{
|
||||
name: "shipping_profiles",
|
||||
name: ["shipping_profile", "shipping_profiles"],
|
||||
args: {
|
||||
entity: "ShippingProfile",
|
||||
},
|
||||
},
|
||||
],
|
||||
} as ModuleJoinerConfig
|
||||
@@ -188,7 +188,7 @@ export class SubscriberLoader {
|
||||
|
||||
const events = Array.isArray(event) ? event : [event]
|
||||
|
||||
const subscriber: Subscriber<T> = async (data: T, eventName: string) => {
|
||||
const subscriber = async (data: T, eventName: string) => {
|
||||
return handler({
|
||||
eventName,
|
||||
data,
|
||||
@@ -200,7 +200,7 @@ export class SubscriberLoader {
|
||||
const subscriberId = this.inferIdentifier(fileName, config, handler)
|
||||
|
||||
for (const e of events) {
|
||||
eventBusService.subscribe(e, subscriber as Subscriber<unknown>, {
|
||||
eventBusService.subscribe(e, subscriber as Subscriber, {
|
||||
...(config.context ?? {}),
|
||||
subscriberId,
|
||||
})
|
||||
|
||||
@@ -128,7 +128,10 @@ export const loadMedusaApp = async (
|
||||
return medusaApp
|
||||
}
|
||||
|
||||
container.register("remoteLink", asValue(medusaApp.link))
|
||||
container.register(
|
||||
ContainerRegistrationKeys.REMOTE_LINK,
|
||||
asValue(medusaApp.link)
|
||||
)
|
||||
container.register(
|
||||
ContainerRegistrationKeys.REMOTE_QUERY,
|
||||
asValue(medusaApp.query)
|
||||
@@ -143,7 +146,7 @@ export const loadMedusaApp = async (
|
||||
|
||||
// Register all unresolved modules as undefined to be present in the container with undefined value by defaul
|
||||
// but still resolvable
|
||||
for (const [, moduleDefinition] of Object.entries(ModulesDefinition)) {
|
||||
for (const moduleDefinition of Object.values(ModulesDefinition)) {
|
||||
if (!container.hasRegistration(moduleDefinition.registrationName)) {
|
||||
container.register(moduleDefinition.registrationName, asValue(undefined))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { EventBusTypes, Logger } from "@medusajs/types"
|
||||
import { EmitData, EventBusTypes, Logger, Message } from "@medusajs/types"
|
||||
import { DatabaseErrorCode, EventBusUtils } from "@medusajs/utils"
|
||||
import { EOL } from "os"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import { StagedJob } from "../models"
|
||||
import { FindConfig } from "../types/common"
|
||||
import { ConfigModule } from "../types/global"
|
||||
import { isString } from "../utils"
|
||||
import { sleep } from "../utils/sleep"
|
||||
import StagedJobService from "./staged-job"
|
||||
import { FindConfig } from "../types/common"
|
||||
import { EOL } from "os"
|
||||
|
||||
type InjectedDependencies = {
|
||||
stagedJobService: StagedJobService
|
||||
@@ -118,6 +118,8 @@ export default class EventBusService
|
||||
*/
|
||||
async emit<T>(data: EventBusTypes.EmitData<T>[]): Promise<StagedJob[] | void>
|
||||
|
||||
async emit<T>(data: EventBusTypes.Message<T>[]): Promise<StagedJob[] | void>
|
||||
|
||||
/**
|
||||
* Calls all subscribers when an event occurs.
|
||||
* @param {string} eventName - the name of the event to be process.
|
||||
@@ -133,7 +135,10 @@ export default class EventBusService
|
||||
|
||||
async emit<
|
||||
T,
|
||||
TInput extends string | EventBusTypes.EmitData<T>[] = string,
|
||||
TInput extends
|
||||
| string
|
||||
| EventBusTypes.EmitData<T>[]
|
||||
| EventBusTypes.Message<T>[] = string,
|
||||
TResult = TInput extends EventBusTypes.EmitData<T>[]
|
||||
? StagedJob[]
|
||||
: StagedJob
|
||||
@@ -144,16 +149,19 @@ export default class EventBusService
|
||||
): Promise<TResult | void> {
|
||||
const manager = this.activeManager_
|
||||
const isBulkEmit = !isString(eventNameOrData)
|
||||
const dataBody = isString(eventNameOrData)
|
||||
? data ?? (data as Message<T>).body
|
||||
: undefined
|
||||
const events: EventBusTypes.EmitData[] = isBulkEmit
|
||||
? eventNameOrData.map((event) => ({
|
||||
eventName: event.eventName,
|
||||
data: event.data,
|
||||
data: (event as EmitData).data ?? (event as Message<T>).body.data,
|
||||
options: event.options,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
eventName: eventNameOrData,
|
||||
data: data,
|
||||
data: dataBody,
|
||||
options: options,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
import { EntityManager, In } from "typeorm"
|
||||
import {
|
||||
IEventBusService,
|
||||
IInventoryService,
|
||||
IStockLocationService,
|
||||
InventoryItemDTO,
|
||||
InventoryLevelDTO,
|
||||
IStockLocationService,
|
||||
RemoteQueryFunction,
|
||||
ReservationItemDTO,
|
||||
ReserveQuantityContext,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
FlagRouter,
|
||||
MedusaError,
|
||||
MedusaV2Flag,
|
||||
isDefined,
|
||||
promiseAll,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import { EntityManager, In } from "typeorm"
|
||||
import { LineItem, Product, ProductVariant } from "../models"
|
||||
import { isDefined, MedusaError, promiseAll } from "@medusajs/utils"
|
||||
import { PricedProduct, PricedVariant } from "../types/pricing"
|
||||
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import { ProductVariantInventoryItem } from "../models/product-variant-inventory-item"
|
||||
import { getSetDifference } from "../utils/diff-set"
|
||||
import ProductVariantService from "./product-variant"
|
||||
import SalesChannelInventoryService from "./sales-channel-inventory"
|
||||
import SalesChannelLocationService from "./sales-channel-location"
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import { getSetDifference } from "../utils/diff-set"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
salesChannelLocationService: SalesChannelLocationService
|
||||
salesChannelInventoryService: SalesChannelInventoryService
|
||||
productVariantService: ProductVariantService
|
||||
stockLocationService: IStockLocationService
|
||||
inventoryService: IInventoryService
|
||||
eventBusService: IEventBusService
|
||||
featureFlagRouter: FlagRouter
|
||||
remoteQuery: RemoteQueryFunction
|
||||
}
|
||||
|
||||
type AvailabilityContext = {
|
||||
@@ -42,6 +50,8 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
protected readonly salesChannelInventoryService_: SalesChannelInventoryService
|
||||
protected readonly productVariantService_: ProductVariantService
|
||||
protected readonly eventBusService_: IEventBusService
|
||||
protected readonly featureFlagRouter_: FlagRouter
|
||||
protected readonly remoteQuery_: RemoteQueryFunction
|
||||
|
||||
protected get inventoryService_(): IInventoryService {
|
||||
return this.__container__.inventoryService
|
||||
@@ -56,6 +66,8 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
salesChannelInventoryService,
|
||||
productVariantService,
|
||||
eventBusService,
|
||||
featureFlagRouter,
|
||||
remoteQuery,
|
||||
}: InjectedDependencies) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
super(arguments[0])
|
||||
@@ -64,6 +76,8 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
this.salesChannelInventoryService_ = salesChannelInventoryService
|
||||
this.productVariantService_ = productVariantService
|
||||
this.eventBusService_ = eventBusService
|
||||
this.featureFlagRouter_ = featureFlagRouter
|
||||
this.remoteQuery_ = remoteQuery
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,16 +321,29 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
// Verify that variant exists
|
||||
const variants = await this.productVariantService_
|
||||
.withTransaction(this.activeManager_)
|
||||
.list(
|
||||
{
|
||||
id: data.map((d) => d.variantId),
|
||||
},
|
||||
{
|
||||
select: ["id"],
|
||||
}
|
||||
let variants
|
||||
if (this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
variants = await this.remoteQuery_(
|
||||
remoteQueryObjectFromString({
|
||||
entryPoint: "variants",
|
||||
variables: {
|
||||
id: data.map((d) => d.variantId),
|
||||
},
|
||||
fields: ["id"],
|
||||
})
|
||||
)
|
||||
} else {
|
||||
variants = await this.productVariantService_
|
||||
.withTransaction(this.activeManager_)
|
||||
.list(
|
||||
{
|
||||
id: data.map((d) => d.variantId),
|
||||
},
|
||||
{
|
||||
select: ["id"],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const foundVariantIds = new Set(variants.map((v) => v.id))
|
||||
const requestedVariantIds = new Set(data.map((v) => v.variantId))
|
||||
@@ -404,7 +431,11 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
): tc is ProductVariantInventoryItem => !!tc
|
||||
)
|
||||
|
||||
return await variantInventoryRepo.save(toCreate)
|
||||
const createdVariantInventoryItems = await variantInventoryRepo.save(
|
||||
toCreate
|
||||
)
|
||||
|
||||
return createdVariantInventoryItems
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import { mergeTypeDefs } from "@graphql-tools/merge"
|
||||
import { makeExecutableSchema } from "@graphql-tools/schema"
|
||||
import { getFieldsAndRelations } from "../../utils"
|
||||
|
||||
const userModule = `
|
||||
type User {
|
||||
id: ID!
|
||||
name: String!
|
||||
blabla: WHATEVER
|
||||
}
|
||||
|
||||
type Post {
|
||||
author: User!
|
||||
}
|
||||
`
|
||||
|
||||
const postModule = `
|
||||
type Post {
|
||||
id: ID!
|
||||
title: String!
|
||||
date: String
|
||||
}
|
||||
|
||||
type User {
|
||||
posts: [Post!]!
|
||||
}
|
||||
|
||||
type WHATEVER {
|
||||
random_field: String
|
||||
post: Post
|
||||
}
|
||||
`
|
||||
|
||||
const mergedSchema = mergeTypeDefs([userModule, postModule])
|
||||
const schema = makeExecutableSchema({
|
||||
typeDefs: mergedSchema,
|
||||
})
|
||||
|
||||
const types = schema.getTypeMap()
|
||||
|
||||
describe("getFieldsAndRelations", function () {
|
||||
it("Should get all fields of a given entity", async function () {
|
||||
const fields = getFieldsAndRelations(types, "User")
|
||||
expect(fields).toEqual(expect.arrayContaining(["id", "name"]))
|
||||
})
|
||||
|
||||
it("Should get all fields of a given entity and a relation", async function () {
|
||||
const fields = getFieldsAndRelations(types, "User", ["posts"])
|
||||
expect(fields).toEqual(
|
||||
expect.arrayContaining([
|
||||
"id",
|
||||
"name",
|
||||
"posts.id",
|
||||
"posts.title",
|
||||
"posts.date",
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("Should get all fields of a given entity and many relations", async function () {
|
||||
const fields = getFieldsAndRelations(types, "User", [
|
||||
"posts",
|
||||
"blabla",
|
||||
"blabla.post",
|
||||
])
|
||||
expect(fields).toEqual(
|
||||
expect.arrayContaining([
|
||||
"id",
|
||||
"name",
|
||||
"posts.id",
|
||||
"posts.title",
|
||||
"posts.date",
|
||||
"blabla.random_field",
|
||||
"blabla.post.id",
|
||||
"blabla.post.title",
|
||||
"blabla.post.date",
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("Should get all fields of a given entity and many relations limited to the relations given", async function () {
|
||||
const fields = getFieldsAndRelations(types, "User", ["posts", "blabla"])
|
||||
expect(fields).toEqual(
|
||||
expect.arrayContaining([
|
||||
"id",
|
||||
"name",
|
||||
"posts.id",
|
||||
"posts.title",
|
||||
"posts.date",
|
||||
"blabla.random_field",
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ModuleJoinerConfig,
|
||||
ModuleServiceInitializeOptions,
|
||||
RemoteJoinerQuery,
|
||||
RemoteQueryFunction,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
@@ -41,6 +42,7 @@ export type RunMigrationFn = (
|
||||
|
||||
export type MedusaModuleConfig = {
|
||||
[key: string | Modules]:
|
||||
| string
|
||||
| boolean
|
||||
| Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
}
|
||||
@@ -182,10 +184,7 @@ function registerCustomJoinerConfigs(servicesConfig: ModuleJoinerConfig[]) {
|
||||
export type MedusaAppOutput = {
|
||||
modules: Record<string, LoadedModule | LoadedModule[]>
|
||||
link: RemoteLink | undefined
|
||||
query: (
|
||||
query: string | RemoteJoinerQuery | object,
|
||||
variables?: Record<string, unknown>
|
||||
) => Promise<any>
|
||||
query: RemoteQueryFunction
|
||||
entitiesMap?: Record<string, any>
|
||||
notFound?: Record<string, Record<string, string>>
|
||||
runMigrations: RunMigrationFn
|
||||
@@ -201,6 +200,7 @@ export async function MedusaApp({
|
||||
linkModules,
|
||||
remoteFetchData,
|
||||
injectedDependencies,
|
||||
onApplicationStartCb,
|
||||
}: {
|
||||
sharedContainer?: MedusaContainer
|
||||
sharedResourcesConfig?: SharedResources
|
||||
@@ -212,6 +212,7 @@ export async function MedusaApp({
|
||||
linkModules?: ModuleJoinerConfig | ModuleJoinerConfig[]
|
||||
remoteFetchData?: RemoteFetchDataCallback
|
||||
injectedDependencies?: any
|
||||
onApplicationStartCb?: () => void
|
||||
} = {}): Promise<{
|
||||
modules: Record<string, LoadedModule | LoadedModule[]>
|
||||
link: RemoteLink | undefined
|
||||
@@ -355,6 +356,6 @@ export async function MedusaApp({
|
||||
runMigrations,
|
||||
}
|
||||
} finally {
|
||||
await MedusaModule.onApplicationStart()
|
||||
MedusaModule.onApplicationStart(onApplicationStartCb)
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,8 @@ export type LinkModuleBootstrapOptions = {
|
||||
}
|
||||
|
||||
export class MedusaModule {
|
||||
private static instances_: Map<string, any> = new Map()
|
||||
private static instances_: Map<string, { [key: string]: IModuleService }> =
|
||||
new Map()
|
||||
private static modules_: Map<string, ModuleAlias[]> = new Map()
|
||||
private static loading_: Map<string, Promise<any>> = new Map()
|
||||
private static joinerConfig_: Map<string, ModuleJoinerConfig> = new Map()
|
||||
@@ -87,12 +88,15 @@ export class MedusaModule {
|
||||
})
|
||||
}
|
||||
|
||||
public static onApplicationStart(): void {
|
||||
public static onApplicationStart(onApplicationStartCb?: () => void): void {
|
||||
for (const instances of MedusaModule.instances_.values()) {
|
||||
for (const instance of Object.values(instances) as IModuleService[]) {
|
||||
if (instance?.__hooks) {
|
||||
instance.__hooks?.onApplicationStart
|
||||
?.bind(instance)()
|
||||
.then(() => {
|
||||
onApplicationStartCb?.()
|
||||
})
|
||||
.catch(() => {
|
||||
// The module should handle this and log it
|
||||
return void 0
|
||||
@@ -217,7 +221,9 @@ export class MedusaModule {
|
||||
)
|
||||
|
||||
if (MedusaModule.instances_.has(hashKey)) {
|
||||
return MedusaModule.instances_.get(hashKey)
|
||||
return MedusaModule.instances_.get(hashKey)! as {
|
||||
[key: string]: T
|
||||
}
|
||||
}
|
||||
|
||||
if (MedusaModule.loading_.has(hashKey)) {
|
||||
@@ -249,7 +255,12 @@ export class MedusaModule {
|
||||
}
|
||||
}
|
||||
|
||||
const container = createMedusaContainer({}, sharedContainer)
|
||||
// TODO: Only do that while legacy modules sharing the manager exists then remove the ternary in favor of createMedusaContainer({}, globalContainer)
|
||||
const container =
|
||||
modDeclaration.scope === MODULE_SCOPE.INTERNAL &&
|
||||
modDeclaration.resources === MODULE_RESOURCE_TYPE.SHARED
|
||||
? sharedContainer ?? createMedusaContainer()
|
||||
: createMedusaContainer({}, sharedContainer)
|
||||
|
||||
if (injectedDependencies) {
|
||||
for (const service in injectedDependencies) {
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { GraphQLNamedType, GraphQLObjectType, isObjectType } from "graphql"
|
||||
|
||||
export function getFieldsAndRelations(
|
||||
schemaTypeMap: { [key: string]: GraphQLNamedType },
|
||||
typeName: string,
|
||||
relations: string[] = []
|
||||
) {
|
||||
const result: string[] = []
|
||||
|
||||
function traverseFields(typeName, prefix) {
|
||||
const type = schemaTypeMap[typeName]
|
||||
|
||||
if (!(type instanceof GraphQLObjectType)) {
|
||||
return
|
||||
}
|
||||
|
||||
const fields = type.getFields()
|
||||
|
||||
for (const fieldName in fields) {
|
||||
const field = fields[fieldName]
|
||||
let fieldType = field.type as any
|
||||
|
||||
while (fieldType.ofType) {
|
||||
fieldType = fieldType.ofType
|
||||
}
|
||||
|
||||
if (!isObjectType(fieldType)) {
|
||||
result.push(`${prefix}${fieldName}`)
|
||||
} else if (relations.includes(prefix + fieldName)) {
|
||||
traverseFields(fieldType.name, `${prefix}${fieldName}.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverseFields(typeName, "")
|
||||
return result
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from "./clean-graphql-schema"
|
||||
export * from "./get-fields-and-relations"
|
||||
export * from "./graphql-schema-to-fields"
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
PriceSet,
|
||||
PriceSetMoneyAmount,
|
||||
} from "@models"
|
||||
import schema from "./schema"
|
||||
|
||||
export const LinkableKeys = {
|
||||
money_amount_id: MoneyAmount.name,
|
||||
@@ -32,45 +33,30 @@ export const joinerConfig: ModuleJoinerConfig = {
|
||||
serviceName: Modules.PRICING,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: LinkableKeys,
|
||||
schema,
|
||||
alias: [
|
||||
{
|
||||
name: "price_set",
|
||||
name: ["price_set", "price_sets"],
|
||||
args: {
|
||||
entity: "PriceSet",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "price_sets",
|
||||
},
|
||||
{
|
||||
name: "money_amount",
|
||||
name: ["money_amount", "money_amounts"],
|
||||
args: {
|
||||
methodSuffix: "MoneyAmounts",
|
||||
entity: "MoneyAmount",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "money_amounts",
|
||||
args: {
|
||||
methodSuffix: "MoneyAmounts",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "currency",
|
||||
name: ["currency", "currencies"],
|
||||
args: {
|
||||
methodSuffix: "Currencies",
|
||||
entity: "Currency",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "currencies",
|
||||
args: {
|
||||
methodSuffix: "Currencies",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "price_list",
|
||||
args: {
|
||||
methodSuffix: "PriceLists",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "price_lists",
|
||||
name: ["price_list", "price_lists"],
|
||||
args: {
|
||||
methodSuffix: "PriceLists",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export const schema = `
|
||||
type PriceSet {
|
||||
id: String!
|
||||
money_amounts: [MoneyAmount]
|
||||
}
|
||||
|
||||
type MoneyAmount {
|
||||
id: String!
|
||||
currency_code: String
|
||||
currency: Currency
|
||||
amount: Float
|
||||
min_quantity: Float
|
||||
max_quantity: Float
|
||||
}
|
||||
`
|
||||
|
||||
export default schema
|
||||
@@ -1,19 +1,27 @@
|
||||
import {
|
||||
EmitData,
|
||||
EventBusTypes,
|
||||
IEventBusModuleService,
|
||||
Message,
|
||||
Subscriber,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export class EventBusService implements IEventBusModuleService {
|
||||
async emit<T>(
|
||||
emit<T>(
|
||||
eventName: string,
|
||||
data: T,
|
||||
options: Record<string, unknown>
|
||||
options?: Record<string, unknown>
|
||||
): Promise<void>
|
||||
emit<T>(data: EmitData<T>[]): Promise<void>
|
||||
emit<T>(data: Message<T>[]): Promise<void>
|
||||
|
||||
async emit<T>(data: EventBusTypes.EmitData<T>[]): Promise<void>
|
||||
|
||||
async emit<T, TInput extends string | EventBusTypes.EmitData<T>[] = string>(
|
||||
async emit<
|
||||
T,
|
||||
TInput extends
|
||||
| string
|
||||
| EventBusTypes.EmitData<T>[]
|
||||
| EventBusTypes.Message<T>[] = string
|
||||
>(
|
||||
eventOrData: TInput,
|
||||
data?: T,
|
||||
options: Record<string, unknown> = {}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { kebabCase } from "@medusajs/utils"
|
||||
import { knex } from "knex"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import * as CustomRepositories from "../__fixtures__/module"
|
||||
import {
|
||||
buildProductAndRelationsData,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
} from "../__fixtures__/product"
|
||||
import { productsData } from "../__fixtures__/product/data"
|
||||
import { DB_URL, TestDatabase } from "../utils"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { getInitModuleConfig } from "../utils/get-init-module-config"
|
||||
|
||||
const sharedPgConnection = knex<any, any>({
|
||||
@@ -238,7 +238,23 @@ describe("Product module", function () {
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
const productsCreated = await module.create([data])
|
||||
|
||||
const products = await module.list(
|
||||
{ id: productsCreated[0].id },
|
||||
{
|
||||
relations: [
|
||||
"images",
|
||||
"categories",
|
||||
"variants",
|
||||
"variants.options",
|
||||
"options",
|
||||
"options.values",
|
||||
"tags",
|
||||
"type",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
expect(products).toHaveLength(1)
|
||||
|
||||
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { Product, ProductCategory } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { getInitModuleConfig, TestDatabase } from "../../../utils"
|
||||
import { Product, ProductCategory } from "@models"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { EventBusService } from "../../../__fixtures__/event-bus"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { productCategoriesRankData } from "../../../__fixtures__/product-category/data"
|
||||
import { EventBusService } from "../../../__fixtures__/event-bus"
|
||||
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
||||
import { initModules } from "medusa-test-utils/dist"
|
||||
import { TestDatabase, getInitModuleConfig } from "../../../utils"
|
||||
|
||||
describe("ProductModuleService product categories", () => {
|
||||
let service: IProductModuleService
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
import { getInitModuleConfig, TestDatabase } from "../../../utils"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { Product, ProductTag } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
||||
import { initModules } from "medusa-test-utils/dist"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { Product, ProductTag } from "@models"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { TestDatabase, getInitModuleConfig } from "../../../utils"
|
||||
|
||||
describe("ProductModuleService product tags", () => {
|
||||
let service: IProductModuleService
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
import { getInitModuleConfig, TestDatabase } from "../../../utils"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ProductType } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
||||
import { initModules } from "medusa-test-utils/dist"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductType } from "@models"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { getInitModuleConfig, TestDatabase } from "../../../utils"
|
||||
|
||||
describe("ProductModuleService product types", () => {
|
||||
let service: IProductModuleService
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
import { getInitModuleConfig, TestDatabase } from "../../../utils"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { Product, ProductVariant } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
|
||||
import { initModules } from "medusa-test-utils/dist"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { Product, ProductVariant } from "@models"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { getInitModuleConfig, TestDatabase } from "../../../utils"
|
||||
|
||||
describe("ProductModuleService product variants", () => {
|
||||
let service: IProductModuleService
|
||||
|
||||
+19
-3
@@ -13,13 +13,13 @@ import {
|
||||
ProductVariant,
|
||||
} from "@models"
|
||||
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { initialize } from "../../../../src"
|
||||
import { EventBusService } from "../../../__fixtures__/event-bus"
|
||||
import { createCollections, createTypes } from "../../../__fixtures__/product"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { buildProductAndRelationsData } from "../../../__fixtures__/product/data/create-product"
|
||||
import { DB_URL, getInitModuleConfig, TestDatabase } from "../../../utils"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { DB_URL, TestDatabase, getInitModuleConfig } from "../../../utils"
|
||||
|
||||
const beforeEach_ = async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
@@ -584,7 +584,23 @@ describe("ProductModuleService products", function () {
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
const productsCreated = await module.create([data])
|
||||
|
||||
const products = await module.list(
|
||||
{ id: productsCreated[0].id },
|
||||
{
|
||||
relations: [
|
||||
"images",
|
||||
"categories",
|
||||
"variants",
|
||||
"variants.options",
|
||||
"options",
|
||||
"options.values",
|
||||
"tags",
|
||||
"type",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
expect(products).toHaveLength(1)
|
||||
expect(products[0].images).toHaveLength(1)
|
||||
|
||||
@@ -43,92 +43,57 @@ export const joinerConfig: ModuleJoinerConfig = {
|
||||
schema: moduleSchema,
|
||||
alias: [
|
||||
{
|
||||
name: "product",
|
||||
},
|
||||
{
|
||||
name: "products",
|
||||
},
|
||||
{
|
||||
name: "variant",
|
||||
name: ["product", "products"],
|
||||
args: {
|
||||
entity: "Product",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["variant", "variants"],
|
||||
args: {
|
||||
entity: "ProductVariant",
|
||||
methodSuffix: "Variants",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variants",
|
||||
args: {
|
||||
methodSuffix: "Variants",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_option",
|
||||
name: ["product_option", "product_options"],
|
||||
args: {
|
||||
entity: "ProductOption",
|
||||
methodSuffix: "Options",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_options",
|
||||
args: {
|
||||
methodSuffix: "Options",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_type",
|
||||
name: ["product_type", "product_types"],
|
||||
args: {
|
||||
entity: "ProductType",
|
||||
methodSuffix: "Types",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_types",
|
||||
args: {
|
||||
methodSuffix: "Types",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_image",
|
||||
name: ["product_image", "product_images"],
|
||||
args: {
|
||||
entity: "ProductImage",
|
||||
methodSuffix: "Images",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_images",
|
||||
args: {
|
||||
methodSuffix: "Images",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_tag",
|
||||
name: ["product_tag", "product_tags"],
|
||||
args: {
|
||||
entity: "ProductTag",
|
||||
methodSuffix: "Tags",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_tags",
|
||||
args: {
|
||||
methodSuffix: "Tags",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_collection",
|
||||
name: ["product_collection", "product_collections"],
|
||||
args: {
|
||||
entity: "ProductCollection",
|
||||
methodSuffix: "Collections",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_collections",
|
||||
args: {
|
||||
methodSuffix: "Collections",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_category",
|
||||
args: {
|
||||
methodSuffix: "Categories",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_categories",
|
||||
name: ["product_category", "product_categories"],
|
||||
args: {
|
||||
entity: "ProductCategory",
|
||||
methodSuffix: "Categories",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Index,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
@@ -14,8 +15,8 @@ import {
|
||||
Unique,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
import Product from "./product"
|
||||
import { DAL } from "@medusajs/types"
|
||||
import Product from "./product"
|
||||
|
||||
type OptionalFields = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
@@ -85,6 +86,11 @@ class ProductCategory {
|
||||
@ManyToMany(() => Product, (product) => product.categories)
|
||||
products = new Collection<Product>(this)
|
||||
|
||||
@OnInit()
|
||||
async onInit() {
|
||||
this.id = generateEntityId(this.id, "pcat")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
async onCreate(args: EventArgs<ProductCategory>) {
|
||||
this.id = generateEntityId(this.id, "pcat")
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Entity,
|
||||
Filter,
|
||||
Index,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
@@ -11,9 +12,9 @@ import {
|
||||
Unique,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import { DALUtils, generateEntityId, kebabCase } from "@medusajs/utils"
|
||||
import Product from "./product"
|
||||
import { DAL } from "@medusajs/types"
|
||||
|
||||
type OptionalRelations = "products"
|
||||
type OptionalFields = DAL.SoftDeletableEntityDateColumns
|
||||
@@ -61,6 +62,11 @@ class ProductCollection {
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at?: Date
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "pcol")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "pcol")
|
||||
|
||||
@@ -5,14 +5,15 @@ import {
|
||||
Filter,
|
||||
Index,
|
||||
ManyToMany,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import { DALUtils, generateEntityId } from "@medusajs/utils"
|
||||
import Product from "./product"
|
||||
import { DAL } from "@medusajs/types"
|
||||
|
||||
type OptionalRelations = "products"
|
||||
type OptionalFields = DAL.SoftDeletableEntityDateColumns
|
||||
@@ -54,6 +55,11 @@ class ProductImage {
|
||||
@ManyToMany(() => Product, (product) => product.images)
|
||||
products = new Collection<Product>(this)
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "img")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "img")
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { DAL } from "@medusajs/types"
|
||||
import { DALUtils, generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Filter,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import { ProductOption, ProductVariant } from "./index"
|
||||
import { DALUtils, generateEntityId } from "@medusajs/utils"
|
||||
import { DAL } from "@medusajs/types"
|
||||
|
||||
type OptionalFields =
|
||||
| "allow_backorder"
|
||||
@@ -72,6 +73,11 @@ class ProductOptionValue {
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at?: Date
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "optval")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
beforeCreate() {
|
||||
this.id = generateEntityId(this.id, "optval")
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Filter,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
@@ -70,6 +71,11 @@ class ProductOption {
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at?: Date
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "opt")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
beforeCreate() {
|
||||
this.id = generateEntityId(this.id, "opt")
|
||||
|
||||
@@ -5,14 +5,15 @@ import {
|
||||
Filter,
|
||||
Index,
|
||||
ManyToMany,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import { DALUtils, generateEntityId } from "@medusajs/utils"
|
||||
import Product from "./product"
|
||||
import { DAL } from "@medusajs/types"
|
||||
|
||||
type OptionalRelations = "products"
|
||||
type OptionalFields = DAL.SoftDeletableEntityDateColumns
|
||||
@@ -53,6 +54,11 @@ class ProductTag {
|
||||
@ManyToMany(() => Product, (product) => product.tags)
|
||||
products = new Collection<Product>(this)
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ptag")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ptag")
|
||||
|
||||
@@ -3,13 +3,14 @@ import {
|
||||
Entity,
|
||||
Filter,
|
||||
Index,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
import { DALUtils, generateEntityId } from "@medusajs/utils"
|
||||
import { DAL } from "@medusajs/types"
|
||||
import { DALUtils, generateEntityId } from "@medusajs/utils"
|
||||
|
||||
type OptionalFields = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
@@ -46,6 +47,11 @@ class ProductType {
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at?: Date
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ptyp")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ptyp")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
Filter,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
@@ -19,7 +21,6 @@ import {
|
||||
} from "@mikro-orm/core"
|
||||
import { Product } from "@models"
|
||||
import ProductOptionValue from "./product-option-value"
|
||||
import { DAL } from "@medusajs/types"
|
||||
|
||||
type OptionalFields =
|
||||
| "allow_backorder"
|
||||
@@ -152,6 +153,11 @@ class ProductVariant {
|
||||
})
|
||||
options = new Collection<ProductOptionValue>(this)
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "variant")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "variant")
|
||||
|
||||
@@ -8,12 +8,14 @@ import {
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
Unique,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
@@ -22,12 +24,11 @@ import {
|
||||
} from "@medusajs/utils"
|
||||
import ProductCategory from "./product-category"
|
||||
import ProductCollection from "./product-collection"
|
||||
import ProductImage from "./product-image"
|
||||
import ProductOption from "./product-option"
|
||||
import ProductTag from "./product-tag"
|
||||
import ProductType from "./product-type"
|
||||
import ProductVariant from "./product-variant"
|
||||
import ProductImage from "./product-image"
|
||||
import { DAL } from "@medusajs/types"
|
||||
|
||||
type OptionalRelations = "collection" | "type"
|
||||
type OptionalFields =
|
||||
@@ -176,6 +177,11 @@ class Product {
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata?: Record<string, unknown> | null
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "prod")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
beforeCreate() {
|
||||
this.id = generateEntityId(this.id, "prod")
|
||||
|
||||
@@ -42,12 +42,6 @@ import {
|
||||
ModulesSdkUtils,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "./../joiner-config"
|
||||
import { ProductEventData, ProductEvents } from "../types/services/product"
|
||||
import {
|
||||
ProductCategoryEventData,
|
||||
ProductCategoryEvents,
|
||||
} from "../types/services/product-category"
|
||||
import {
|
||||
ProductCategoryServiceTypes,
|
||||
ProductCollectionServiceTypes,
|
||||
@@ -55,6 +49,12 @@ import {
|
||||
ProductServiceTypes,
|
||||
ProductVariantServiceTypes,
|
||||
} from "@types"
|
||||
import { ProductEventData, ProductEvents } from "../types/services/product"
|
||||
import {
|
||||
ProductCategoryEventData,
|
||||
ProductCategoryEvents,
|
||||
} from "../types/services/product-category"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "./../joiner-config"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
@@ -371,52 +371,56 @@ export default class ProductModuleService<
|
||||
async createTags(
|
||||
data: ProductTypes.CreateProductTagDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
): Promise<ProductTypes.ProductTagDTO[]> {
|
||||
const productTags = await this.productTagService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productTags))
|
||||
return await this.baseRepository_.serialize(productTags, { populate: true })
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updateTags(
|
||||
data: ProductTypes.UpdateProductTagDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
): Promise<ProductTypes.ProductTagDTO[]> {
|
||||
const productTags = await this.productTagService_.update(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productTags))
|
||||
return await this.baseRepository_.serialize(productTags, { populate: true })
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async createTypes(
|
||||
data: ProductTypes.CreateProductTypeDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
): Promise<ProductTypes.ProductTypeDTO[]> {
|
||||
const productTypes = await this.productTypeService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productTypes))
|
||||
return await this.baseRepository_.serialize(productTypes, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updateTypes(
|
||||
data: ProductTypes.UpdateProductTypeDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
): Promise<ProductTypes.ProductTypeDTO[]> {
|
||||
const productTypes = await this.productTypeService_.update(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productTypes))
|
||||
return await this.baseRepository_.serialize(productTypes, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
@@ -476,7 +480,7 @@ export default class ProductModuleService<
|
||||
async updateCollections(
|
||||
data: ProductTypes.UpdateProductCollectionDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
): Promise<ProductTypes.ProductCollectionDTO[]> {
|
||||
const productCollections = await this.productCollectionService_.update(
|
||||
data,
|
||||
sharedContext
|
||||
@@ -492,14 +496,16 @@ export default class ProductModuleService<
|
||||
}))
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productCollections))
|
||||
return await this.baseRepository_.serialize(productCollections, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async createCategory(
|
||||
data: ProductCategoryServiceTypes.CreateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
): Promise<ProductTypes.ProductCategoryDTO> {
|
||||
const productCategory = await this.productCategoryService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
@@ -510,7 +516,9 @@ export default class ProductModuleService<
|
||||
{ id: productCategory.id }
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productCategory))
|
||||
return await this.baseRepository_.serialize(productCategory, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
@@ -518,7 +526,7 @@ export default class ProductModuleService<
|
||||
categoryId: string,
|
||||
data: ProductCategoryServiceTypes.UpdateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
): Promise<ProductTypes.ProductCategoryDTO> {
|
||||
const productCategory = await this.productCategoryService_.update(
|
||||
categoryId,
|
||||
data,
|
||||
@@ -530,7 +538,9 @@ export default class ProductModuleService<
|
||||
{ id: productCategory.id }
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productCategory))
|
||||
return await this.baseRepository_.serialize(productCategory, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { IPromotionModuleService } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
||||
import { MikroOrmWrapper } from "../../../utils"
|
||||
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
||||
import { initModules } from "medusa-test-utils/dist"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
|
||||
@@ -1,25 +1,51 @@
|
||||
export type Subscriber<T = unknown> = (
|
||||
data: T,
|
||||
eventName: string
|
||||
) => Promise<void>
|
||||
|
||||
export type SubscriberContext = {
|
||||
subscriberId: string
|
||||
data: T,
|
||||
eventName: string
|
||||
) => Promise<void>
|
||||
|
||||
export type SubscriberContext = {
|
||||
subscriberId: string
|
||||
}
|
||||
|
||||
export type SubscriberDescriptor = {
|
||||
id: string
|
||||
subscriber: Subscriber
|
||||
}
|
||||
|
||||
export type EventHandler<T = unknown> = (
|
||||
data: T,
|
||||
eventName: string
|
||||
) => Promise<void>
|
||||
|
||||
export type EmitData<T = unknown> = {
|
||||
eventName: string
|
||||
data: T
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type MessageBody<T = unknown> = {
|
||||
metadata: {
|
||||
service: string
|
||||
action: string
|
||||
object: string
|
||||
eventGroupId?: string
|
||||
}
|
||||
|
||||
export type SubscriberDescriptor = {
|
||||
id: string
|
||||
subscriber: Subscriber
|
||||
data: T
|
||||
}
|
||||
|
||||
export type Message<T = unknown> = {
|
||||
eventName: string
|
||||
body: MessageBody<T>
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type MessageFormat<T = unknown> = {
|
||||
eventName: string
|
||||
metadata: {
|
||||
service: string
|
||||
action: string
|
||||
object: string
|
||||
eventGroupId?: string
|
||||
}
|
||||
|
||||
export type EventHandler<T = unknown> = (
|
||||
data: T,
|
||||
eventName: string
|
||||
) => Promise<void>
|
||||
|
||||
export type EmitData<T = unknown> = {
|
||||
eventName: string
|
||||
data: T
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
|
||||
data: T | T[]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EmitData, Subscriber, SubscriberContext } from "./common"
|
||||
import { EmitData, Message, Subscriber, SubscriberContext } from "./common"
|
||||
|
||||
export interface IEventBusModuleService {
|
||||
emit<T>(
|
||||
@@ -7,6 +7,7 @@ export interface IEventBusModuleService {
|
||||
options?: Record<string, unknown>
|
||||
): Promise<void>
|
||||
emit<T>(data: EmitData<T>[]): Promise<void>
|
||||
emit<T>(data: Message<T>[]): Promise<void>
|
||||
|
||||
subscribe(
|
||||
eventName: string | symbol,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Subscriber, SubscriberContext } from "."
|
||||
import { ITransactionBaseService } from "../transaction-base/transaction-base"
|
||||
import { ITransactionBaseService } from "../transaction-base"
|
||||
import { EmitData, Message, Subscriber, SubscriberContext } from "./common"
|
||||
|
||||
export interface IEventBusService extends ITransactionBaseService {
|
||||
subscribe(
|
||||
@@ -13,5 +13,8 @@ export interface IEventBusService extends ITransactionBaseService {
|
||||
subscriber: Subscriber,
|
||||
context?: SubscriberContext
|
||||
): this
|
||||
|
||||
emit<T>(event: string, data: T, options?: unknown): Promise<unknown | void>
|
||||
emit<T>(data: EmitData<T>[]): Promise<unknown | void>
|
||||
emit<T>(data: Message<T>[]): Promise<unknown | void>
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,11 +1,9 @@
|
||||
import { FindConfig } from "../common"
|
||||
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
||||
import { ModuleJoinerConfig } from "../modules-sdk"
|
||||
import { IModuleService } from "../modules-sdk"
|
||||
import { Context } from "../shared-context"
|
||||
|
||||
export interface ILinkModule {
|
||||
__joinerConfig(): ModuleJoinerConfig
|
||||
|
||||
export interface ILinkModule extends IModuleService {
|
||||
list(
|
||||
filters?: Record<string, unknown>,
|
||||
config?: FindConfig<unknown>,
|
||||
|
||||
@@ -45,16 +45,11 @@ import {
|
||||
} from "./common"
|
||||
|
||||
import { FindConfig } from "../common"
|
||||
import { ModuleJoinerConfig } from "../modules-sdk"
|
||||
import { Context } from "../shared-context"
|
||||
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
||||
import { IModuleService } from "../modules-sdk"
|
||||
import { Context } from "../shared-context"
|
||||
|
||||
export interface IPricingModuleService {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
__joinerConfig(): ModuleJoinerConfig
|
||||
|
||||
export interface IPricingModuleService extends IModuleService {
|
||||
/**
|
||||
* This method is used to calculate prices based on the provided filters and context.
|
||||
*
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
||||
import {
|
||||
CreateProductCategoryDTO,
|
||||
CreateProductCollectionDTO,
|
||||
@@ -28,18 +29,12 @@ import {
|
||||
UpdateProductTypeDTO,
|
||||
UpdateProductVariantDTO,
|
||||
} from "./common"
|
||||
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
||||
|
||||
import { Context } from "../shared-context"
|
||||
import { FindConfig } from "../common"
|
||||
import { ModuleJoinerConfig } from "../modules-sdk"
|
||||
|
||||
export interface IProductModuleService {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
__joinerConfig(): ModuleJoinerConfig
|
||||
import { IModuleService } from "../modules-sdk"
|
||||
import { Context } from "../shared-context"
|
||||
|
||||
export interface IProductModuleService extends IModuleService {
|
||||
/**
|
||||
* This method is used to retrieve a product by its ID
|
||||
*
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
import { Message } from "./event-bus"
|
||||
|
||||
/**
|
||||
* @deprecated use `Context` instead
|
||||
* @interface
|
||||
*
|
||||
* A context used to share resources, such as transaction manager, between the application and the module.
|
||||
@@ -16,9 +18,19 @@ export type SharedContext = {
|
||||
manager?: EntityManager
|
||||
}
|
||||
|
||||
export interface MessageAggregatorFormat {
|
||||
groupBy?: string[]
|
||||
sortBy?: { [key: string]: string[] | string | number }
|
||||
}
|
||||
|
||||
export interface IMessageAggregator {
|
||||
save(msg: Message | Message[]): void
|
||||
getMessages(format?: MessageAggregatorFormat): Record<string, Message[]>
|
||||
clearMessages(): void
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* A context used to share resources, such as transaction manager, between the application and the module.
|
||||
*/
|
||||
export type Context<TManager = unknown> = {
|
||||
@@ -39,11 +51,20 @@ export type Context<TManager = unknown> = {
|
||||
* A boolean value indicating whether nested transactions are enabled.
|
||||
*/
|
||||
enableNestedTransactions?: boolean
|
||||
/**
|
||||
* A string indicating the ID of the group to aggregate the events to be emitted at a later point.
|
||||
*/
|
||||
eventGroupId?: string
|
||||
/**
|
||||
* A string indicating the ID of the current transaction.
|
||||
*/
|
||||
transactionId?: string
|
||||
|
||||
/**
|
||||
* An instance of a message aggregator, which is used to aggregate messages to be emitted at a later point.
|
||||
*/
|
||||
messageAggregator?: IMessageAggregator
|
||||
|
||||
/**
|
||||
* A string indicating the ID of the current request.
|
||||
*/
|
||||
|
||||
@@ -6,74 +6,69 @@ import {
|
||||
} from "./common"
|
||||
|
||||
import { FindConfig } from "../common/common"
|
||||
import { ModuleJoinerConfig } from "../modules-sdk"
|
||||
import { IModuleService } from "../modules-sdk"
|
||||
import { SharedContext } from "../shared-context"
|
||||
|
||||
export interface IStockLocationService {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
__joinerConfig(): ModuleJoinerConfig
|
||||
|
||||
export interface IStockLocationService extends IModuleService {
|
||||
/**
|
||||
* This method is used to retrieve a paginated list of stock locations based on optional filters and configuration.
|
||||
*
|
||||
*
|
||||
* @param {FilterableStockLocationProps} selector - The filters to apply on the retrieved stock locations.
|
||||
* @param {FindConfig<StockLocationDTO>} config -
|
||||
* @param {FindConfig<StockLocationDTO>} config -
|
||||
* The configurations determining how the stock locations are retrieved. Its properties, such as `select` or `relations`, accept the
|
||||
* attributes or relations associated with a stock location.
|
||||
* @param {SharedContext} context - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @return {Promise<StockLocationDTO[]>} The list of stock locations.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* To retrieve a list of stock locations using their IDs:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function listStockLocations (ids: string[]) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const stockLocations = await stockLocationModule.list({
|
||||
* id: ids
|
||||
* })
|
||||
*
|
||||
*
|
||||
* // do something with the stock locations or return them
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To specify relations that should be retrieved within the stock locations:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function listStockLocations (ids: string[]) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const stockLocations = await stockLocationModule.list({
|
||||
* id: ids
|
||||
* }, {
|
||||
* relations: ["address"]
|
||||
* })
|
||||
*
|
||||
*
|
||||
* // do something with the stock locations or return them
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* By default, only the first `10` records are retrieved. You can control pagination by specifying the `skip` and `take` properties of the `config` parameter:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function listStockLocations (ids: string[], skip: number, take: number) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const stockLocations = await stockLocationModule.list({
|
||||
* id: ids
|
||||
* }, {
|
||||
@@ -81,7 +76,7 @@ export interface IStockLocationService {
|
||||
* skip,
|
||||
* take
|
||||
* })
|
||||
*
|
||||
*
|
||||
* // do something with the stock locations or return them
|
||||
* }
|
||||
* ```
|
||||
@@ -94,63 +89,63 @@ export interface IStockLocationService {
|
||||
|
||||
/**
|
||||
* This method is used to retrieve a paginated list of stock locations along with the total count of available stock locations satisfying the provided filters.
|
||||
*
|
||||
*
|
||||
* @param {FilterableStockLocationProps} selector - The filters to apply on the retrieved stock locations.
|
||||
* @param {FindConfig<StockLocationDTO>} config -
|
||||
* @param {FindConfig<StockLocationDTO>} config -
|
||||
* The configurations determining how the stock locations are retrieved. Its properties, such as `select` or `relations`, accept the
|
||||
* attributes or relations associated with a stock location.
|
||||
* @param {SharedContext} context - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @return {Promise<[StockLocationDTO[], number]>} The list of stock locations along with the total count.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* To retrieve a list of stock locations using their IDs:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function listStockLocations (ids: string[]) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const [stockLocations, count] = await stockLocationModule.listAndCount({
|
||||
* id: ids
|
||||
* })
|
||||
*
|
||||
*
|
||||
* // do something with the stock locations or return them
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To specify relations that should be retrieved within the stock locations:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function listStockLocations (ids: string[]) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const [stockLocations, count] = await stockLocationModule.listAndCount({
|
||||
* id: ids
|
||||
* }, {
|
||||
* relations: ["address"]
|
||||
* })
|
||||
*
|
||||
*
|
||||
* // do something with the stock locations or return them
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* By default, only the first `10` records are retrieved. You can control pagination by specifying the `skip` and `take` properties of the `config` parameter:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function listStockLocations (ids: string[], skip: number, take: number) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const [stockLocations, count] = await stockLocationModule.listAndCount({
|
||||
* id: ids
|
||||
* }, {
|
||||
@@ -158,7 +153,7 @@ export interface IStockLocationService {
|
||||
* skip,
|
||||
* take
|
||||
* })
|
||||
*
|
||||
*
|
||||
* // do something with the stock locations or return them
|
||||
* }
|
||||
* ```
|
||||
@@ -171,45 +166,45 @@ export interface IStockLocationService {
|
||||
|
||||
/**
|
||||
* This method is used to retrieve a stock location by its ID
|
||||
*
|
||||
*
|
||||
* @param {string} id - The ID of the stock location
|
||||
* @param {FindConfig<StockLocationDTO>} config -
|
||||
* @param {FindConfig<StockLocationDTO>} config -
|
||||
* The configurations determining how the stock location is retrieved. Its properties, such as `select` or `relations`, accept the
|
||||
* attributes or relations associated with a stock location.
|
||||
* @param {SharedContext} context - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<StockLocationDTO>} The stock location's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* A simple example that retrieves a inventory item by its ID:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function retrieveStockLocation (id: string) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const stockLocation = await stockLocationModule.retrieve(id)
|
||||
*
|
||||
*
|
||||
* // do something with the stock location or return it
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To specify relations that should be retrieved:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function retrieveStockLocation (id: string) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const stockLocation = await stockLocationModule.retrieve(id, {
|
||||
* relations: ["address"]
|
||||
* })
|
||||
*
|
||||
*
|
||||
* // do something with the stock location or return it
|
||||
* }
|
||||
* ```
|
||||
@@ -222,23 +217,23 @@ export interface IStockLocationService {
|
||||
|
||||
/**
|
||||
* This method is used to create a stock location.
|
||||
*
|
||||
*
|
||||
* @param {CreateStockLocationInput} input - The details of the stock location to create.
|
||||
* @param {SharedContext} context - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<StockLocationDTO>} The created stock location's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function createStockLocation (name: string) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const stockLocation = await stockLocationModule.create({
|
||||
* name
|
||||
* })
|
||||
*
|
||||
*
|
||||
* // do something with the stock location or return it
|
||||
* }
|
||||
*/
|
||||
@@ -249,24 +244,24 @@ export interface IStockLocationService {
|
||||
|
||||
/**
|
||||
* This method is used to update a stock location.
|
||||
*
|
||||
*
|
||||
* @param {string} id - The ID of the stock location.
|
||||
* @param {UpdateStockLocationInput} input - The attributes to update in the stock location.
|
||||
* @param {SharedContext} context - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<StockLocationDTO>} The stock location's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function updateStockLocation (id:string, name: string) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* const stockLocation = await stockLocationModule.update(id, {
|
||||
* name
|
||||
* })
|
||||
*
|
||||
*
|
||||
* // do something with the stock location or return it
|
||||
* }
|
||||
*/
|
||||
@@ -278,19 +273,19 @@ export interface IStockLocationService {
|
||||
|
||||
/**
|
||||
* This method is used to delete a stock location.
|
||||
*
|
||||
*
|
||||
* @param {string} id - The ID of the stock location.
|
||||
* @param {SharedContext} context - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<void>} Resolves when the stock location is successfully deleted.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* import {
|
||||
* initialize as initializeStockLocationModule,
|
||||
* } from "@medusajs/stock-location"
|
||||
*
|
||||
*
|
||||
* async function deleteStockLocation (id:string) {
|
||||
* const stockLocationModule = await initializeStockLocationModule({})
|
||||
*
|
||||
*
|
||||
* await stockLocationModule.delete(id)
|
||||
* }
|
||||
*/
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export const camelToSnakeCase = (string) =>
|
||||
string.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
||||
string.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase()
|
||||
@@ -0,0 +1,157 @@
|
||||
import { MessageAggregator } from "../message-aggregator"
|
||||
|
||||
describe("MessageAggregator", function () {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks
|
||||
})
|
||||
|
||||
it("should group messages by any given group of keys", function () {
|
||||
const aggregator = new MessageAggregator()
|
||||
aggregator.save({
|
||||
eventName: "ProductVariant.created",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "created",
|
||||
object: "ProductVariant",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 999 },
|
||||
},
|
||||
})
|
||||
aggregator.save({
|
||||
eventName: "Product.created",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "created",
|
||||
object: "Product",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 1 },
|
||||
},
|
||||
})
|
||||
aggregator.save({
|
||||
eventName: "ProductVariant.created",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "created",
|
||||
object: "ProductVariant",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 222 },
|
||||
},
|
||||
})
|
||||
aggregator.save({
|
||||
eventName: "ProductType.detached",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "detached",
|
||||
object: "ProductType",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 333 },
|
||||
},
|
||||
})
|
||||
aggregator.save({
|
||||
eventName: "ProductVariant.updated",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "updated",
|
||||
object: "ProductVariant",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 123 },
|
||||
},
|
||||
})
|
||||
|
||||
const format = {
|
||||
groupBy: ["eventName", "body.metadata.object", "body.metadata.action"],
|
||||
sortBy: {
|
||||
"body.metadata.object": ["ProductType", "ProductVariant", "Product"],
|
||||
"body.data.id": "asc",
|
||||
},
|
||||
}
|
||||
|
||||
const messages = aggregator.getMessages(format)
|
||||
|
||||
expect(Object.keys(messages)).toHaveLength(4)
|
||||
|
||||
const allGroups = Object.values(messages)
|
||||
|
||||
expect(allGroups[0]).toEqual([
|
||||
{
|
||||
eventName: "ProductType.detached",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "detached",
|
||||
object: "ProductType",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 333 },
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(allGroups[1]).toEqual([
|
||||
{
|
||||
eventName: "ProductVariant.updated",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "updated",
|
||||
object: "ProductVariant",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 123 },
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(allGroups[2]).toEqual([
|
||||
{
|
||||
eventName: "ProductVariant.created",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "created",
|
||||
object: "ProductVariant",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 222 },
|
||||
},
|
||||
},
|
||||
{
|
||||
eventName: "ProductVariant.created",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "created",
|
||||
object: "ProductVariant",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 999 },
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(allGroups[3]).toEqual([
|
||||
{
|
||||
eventName: "Product.created",
|
||||
body: {
|
||||
metadata: {
|
||||
service: "ProductService",
|
||||
action: "created",
|
||||
object: "Product",
|
||||
eventGroupId: "1",
|
||||
},
|
||||
data: { id: 1 },
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Context, EventBusTypes } from "@medusajs/types"
|
||||
import { CommonEvents } from "./common-events"
|
||||
|
||||
/**
|
||||
* Build messages from message data to be consumed by the event bus and emitted to the consumer
|
||||
* @param MessageFormat
|
||||
* @param options
|
||||
*/
|
||||
export function buildEventMessages<T>(
|
||||
messageData:
|
||||
| EventBusTypes.MessageFormat<T>
|
||||
| EventBusTypes.MessageFormat<T>[],
|
||||
options?: Record<string, unknown>
|
||||
): EventBusTypes.Message<T>[] {
|
||||
const messageData_ = Array.isArray(messageData) ? messageData : [messageData]
|
||||
const messages: EventBusTypes.Message<T>[] = []
|
||||
|
||||
messageData_.map((data) => {
|
||||
const data_ = Array.isArray(data.data) ? data.data : [data.data]
|
||||
data_.forEach((bodyData) => {
|
||||
const message = {
|
||||
eventName: data.eventName,
|
||||
body: {
|
||||
metadata: data.metadata,
|
||||
data: bodyData,
|
||||
},
|
||||
options,
|
||||
}
|
||||
messages.push(message)
|
||||
})
|
||||
})
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to compose and normalize a Message to be emitted by EventBus Module
|
||||
* @param eventName Name of the event to be emitted
|
||||
* @param data The content of the message
|
||||
* @param metadata Metadata of the message
|
||||
* @param context Context from the caller service
|
||||
* @param options Options to be passed to the event bus
|
||||
*/
|
||||
export function composeMessage(
|
||||
eventName: string,
|
||||
{
|
||||
data,
|
||||
service,
|
||||
entity,
|
||||
action,
|
||||
context = {},
|
||||
options,
|
||||
}: {
|
||||
data: unknown
|
||||
service: string
|
||||
entity: string
|
||||
action?: string
|
||||
context?: Context
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
): EventBusTypes.Message {
|
||||
const act = action || eventName.split(".").pop()
|
||||
if (!action && !Object.values(CommonEvents).includes(act as CommonEvents)) {
|
||||
throw new Error("Action is required if eventName is not a CommonEvent")
|
||||
}
|
||||
|
||||
const metadata: EventBusTypes.MessageBody["metadata"] = {
|
||||
service,
|
||||
object: entity,
|
||||
action: act!,
|
||||
}
|
||||
|
||||
if (context.eventGroupId) {
|
||||
metadata.eventGroupId = context.eventGroupId
|
||||
}
|
||||
|
||||
return {
|
||||
eventName,
|
||||
body: {
|
||||
metadata,
|
||||
data,
|
||||
},
|
||||
options,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum CommonEvents {
|
||||
CREATED = "created",
|
||||
UPDATED = "updated",
|
||||
DELETED = "deleted",
|
||||
ATTACHED = "attached",
|
||||
DETACHED = "detached",
|
||||
}
|
||||
@@ -22,6 +22,7 @@ export abstract class AbstractEventBusModuleService
|
||||
options: Record<string, unknown>
|
||||
): Promise<void>
|
||||
abstract emit<T>(data: EventBusTypes.EmitData<T>[]): Promise<void>
|
||||
abstract emit<T>(data: EventBusTypes.Message<T>[]): Promise<void>
|
||||
|
||||
protected storeSubscribers({
|
||||
event,
|
||||
@@ -101,3 +102,7 @@ export abstract class AbstractEventBusModuleService
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export * from "./build-event-messages"
|
||||
export * from "./common-events"
|
||||
export * from "./message-aggregator"
|
||||
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
IMessageAggregator,
|
||||
Message,
|
||||
MessageAggregatorFormat,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export class MessageAggregator implements IMessageAggregator {
|
||||
private messages: Message[]
|
||||
|
||||
constructor() {
|
||||
this.messages = []
|
||||
}
|
||||
|
||||
save(msg: Message | Message[]): void {
|
||||
if (!msg || (Array.isArray(msg) && msg.length === 0)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Array.isArray(msg)) {
|
||||
this.messages.push(...msg)
|
||||
} else {
|
||||
this.messages.push(msg)
|
||||
}
|
||||
}
|
||||
|
||||
getMessages(format?: MessageAggregatorFormat): {
|
||||
[group: string]: Message[]
|
||||
} {
|
||||
const { groupBy, sortBy } = format ?? {}
|
||||
|
||||
if (sortBy) {
|
||||
this.messages.sort((a, b) => this.compareMessages(a, b, sortBy))
|
||||
}
|
||||
|
||||
let messages: { [group: string]: Message[] } = { default: this.messages }
|
||||
|
||||
if (groupBy) {
|
||||
const groupedMessages = this.messages.reduce<{
|
||||
[key: string]: Message[]
|
||||
}>((acc, msg) => {
|
||||
const key = groupBy
|
||||
.map((field) => this.getValueFromPath(msg, field))
|
||||
.join("-")
|
||||
if (!acc[key]) {
|
||||
acc[key] = []
|
||||
}
|
||||
acc[key].push(msg)
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
messages = groupedMessages
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
clearMessages(): void {
|
||||
this.messages = []
|
||||
}
|
||||
|
||||
private getValueFromPath(obj: any, path: string): any {
|
||||
const keys = path.split(".")
|
||||
for (const key of keys) {
|
||||
obj = obj[key]
|
||||
if (obj === undefined) break
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
private compareMessages(
|
||||
a: Message,
|
||||
b: Message,
|
||||
sortBy: MessageAggregatorFormat["sortBy"]
|
||||
): number {
|
||||
for (const key of Object.keys(sortBy!)) {
|
||||
const orderCriteria = sortBy![key]
|
||||
const valueA = this.getValueFromPath(a, key)
|
||||
const valueB = this.getValueFromPath(b, key)
|
||||
|
||||
// User defined order
|
||||
if (Array.isArray(orderCriteria)) {
|
||||
const indexA = orderCriteria.indexOf(valueA)
|
||||
const indexB = orderCriteria.indexOf(valueB)
|
||||
|
||||
if (indexA === indexB) {
|
||||
continue
|
||||
} else if (indexA === -1) {
|
||||
return 1
|
||||
} else if (indexB === -1) {
|
||||
return -1
|
||||
} else {
|
||||
return indexA - indexB
|
||||
}
|
||||
} else {
|
||||
// Ascending or descending order
|
||||
let orderMultiplier = 1
|
||||
if (orderCriteria === "desc" || orderCriteria === -1) {
|
||||
orderMultiplier = -1
|
||||
}
|
||||
|
||||
if (valueA === valueB) {
|
||||
continue
|
||||
} else if (valueA < valueB) {
|
||||
return -1 * orderMultiplier
|
||||
} else {
|
||||
return 1 * orderMultiplier
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
export function InjectIntoContext(
|
||||
properties: Record<string, unknown | Function>
|
||||
): MethodDecorator {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: any
|
||||
): void {
|
||||
if (!target.MedusaContextIndex_) {
|
||||
throw new Error(
|
||||
`To apply @InjectIntoContext you have to flag a parameter using @MedusaContext`
|
||||
)
|
||||
}
|
||||
|
||||
const argIndex = target.MedusaContextIndex_[propertyKey]
|
||||
const original = descriptor.value
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
for (const key of Object.keys(properties)) {
|
||||
args[argIndex] = args[argIndex] ?? {}
|
||||
args[argIndex][key] =
|
||||
args[argIndex][key] ??
|
||||
(typeof properties[key] === "function"
|
||||
? (properties[key] as Function).apply(this, args)
|
||||
: properties[key])
|
||||
}
|
||||
|
||||
return await original.apply(this, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user