Chore/rm main entity concept (#7709)
**What** Update the `MedusaService` class, factory and types to remove the concept of main modules. The idea being that all method will be explicitly named and suffixes to represent the object you are trying to manipulate. This pr also includes various fixes in different modules Co-authored-by: Stevche Radevski <4820812+sradevski@users.noreply.github.com> Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2895ccfba8
commit
48963f55ef
@@ -0,0 +1,282 @@
|
||||
import { defineJoinerConfig } from "../joiner-config-builder"
|
||||
import { Modules } from "../definition"
|
||||
|
||||
const FulfillmentSet = {
|
||||
name: "FulfillmentSet",
|
||||
}
|
||||
const ShippingOption = {
|
||||
name: "ShippingOption",
|
||||
}
|
||||
const ShippingProfile = {
|
||||
name: "ShippingProfile",
|
||||
}
|
||||
const Fulfillment = {
|
||||
name: "Fulfillment",
|
||||
}
|
||||
const FulfillmentProvider = {
|
||||
name: "FulfillmentProvider",
|
||||
}
|
||||
const ServiceZone = {
|
||||
name: "ServiceZone",
|
||||
}
|
||||
const GeoZone = {
|
||||
name: "GeoZone",
|
||||
}
|
||||
const ShippingOptionRule = {
|
||||
name: "ShippingOptionRule",
|
||||
}
|
||||
|
||||
describe("defineJoiner", () => {
|
||||
it("should return a full joiner configuration", () => {
|
||||
const joinerConfig = defineJoinerConfig(Modules.FULFILLMENT, {
|
||||
entityQueryingConfig: [
|
||||
FulfillmentSet,
|
||||
ShippingOption,
|
||||
ShippingProfile,
|
||||
Fulfillment,
|
||||
FulfillmentProvider,
|
||||
ServiceZone,
|
||||
GeoZone,
|
||||
ShippingOptionRule,
|
||||
],
|
||||
})
|
||||
|
||||
expect(joinerConfig).toEqual({
|
||||
serviceName: Modules.FULFILLMENT,
|
||||
primaryKeys: ["id"],
|
||||
schema: undefined,
|
||||
linkableKeys: {
|
||||
fulfillment_set_id: FulfillmentSet.name,
|
||||
shipping_option_id: ShippingOption.name,
|
||||
shipping_profile_id: ShippingProfile.name,
|
||||
fulfillment_id: Fulfillment.name,
|
||||
fulfillment_provider_id: FulfillmentProvider.name,
|
||||
service_zone_id: ServiceZone.name,
|
||||
geo_zone_id: GeoZone.name,
|
||||
shipping_option_rule_id: ShippingOptionRule.name,
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: ["fulfillment_set", "fulfillment_sets"],
|
||||
args: {
|
||||
entity: FulfillmentSet.name,
|
||||
methodSuffix: "FulfillmentSets",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["shipping_option", "shipping_options"],
|
||||
args: {
|
||||
entity: ShippingOption.name,
|
||||
methodSuffix: "ShippingOptions",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["shipping_profile", "shipping_profiles"],
|
||||
args: {
|
||||
entity: ShippingProfile.name,
|
||||
methodSuffix: "ShippingProfiles",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["fulfillment", "fulfillments"],
|
||||
args: {
|
||||
entity: Fulfillment.name,
|
||||
methodSuffix: "Fulfillments",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["fulfillment_provider", "fulfillment_providers"],
|
||||
args: {
|
||||
entity: FulfillmentProvider.name,
|
||||
methodSuffix: "FulfillmentProviders",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["service_zone", "service_zones"],
|
||||
args: {
|
||||
entity: ServiceZone.name,
|
||||
methodSuffix: "ServiceZones",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["geo_zone", "geo_zones"],
|
||||
args: {
|
||||
entity: GeoZone.name,
|
||||
methodSuffix: "GeoZones",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["shipping_option_rule", "shipping_option_rules"],
|
||||
args: {
|
||||
entity: ShippingOptionRule.name,
|
||||
methodSuffix: "ShippingOptionRules",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should return a full joiner configuration with custom aliases", () => {
|
||||
const joinerConfig = defineJoinerConfig(Modules.FULFILLMENT, {
|
||||
alias: [
|
||||
{
|
||||
name: ["custom", "customs"],
|
||||
args: {
|
||||
entity: "Custom",
|
||||
methodSuffix: "Customs",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(joinerConfig).toEqual({
|
||||
serviceName: Modules.FULFILLMENT,
|
||||
primaryKeys: ["id"],
|
||||
schema: undefined,
|
||||
linkableKeys: {},
|
||||
alias: [
|
||||
{
|
||||
name: ["custom", "customs"],
|
||||
args: {
|
||||
entity: "Custom",
|
||||
methodSuffix: "Customs",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should return a full joiner configuration with custom aliases and models", () => {
|
||||
const joinerConfig = defineJoinerConfig(Modules.FULFILLMENT, {
|
||||
entityQueryingConfig: [
|
||||
FulfillmentSet,
|
||||
ShippingOption,
|
||||
ShippingProfile,
|
||||
Fulfillment,
|
||||
FulfillmentProvider,
|
||||
ServiceZone,
|
||||
GeoZone,
|
||||
ShippingOptionRule,
|
||||
],
|
||||
alias: [
|
||||
{
|
||||
name: ["custom", "customs"],
|
||||
args: {
|
||||
entity: "Custom",
|
||||
methodSuffix: "Customs",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(joinerConfig).toEqual({
|
||||
serviceName: Modules.FULFILLMENT,
|
||||
primaryKeys: ["id"],
|
||||
schema: undefined,
|
||||
linkableKeys: {
|
||||
fulfillment_set_id: FulfillmentSet.name,
|
||||
shipping_option_id: ShippingOption.name,
|
||||
shipping_profile_id: ShippingProfile.name,
|
||||
fulfillment_id: Fulfillment.name,
|
||||
fulfillment_provider_id: FulfillmentProvider.name,
|
||||
service_zone_id: ServiceZone.name,
|
||||
geo_zone_id: GeoZone.name,
|
||||
shipping_option_rule_id: ShippingOptionRule.name,
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: ["custom", "customs"],
|
||||
args: {
|
||||
entity: "Custom",
|
||||
methodSuffix: "Customs",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["fulfillment_set", "fulfillment_sets"],
|
||||
args: {
|
||||
entity: FulfillmentSet.name,
|
||||
methodSuffix: "FulfillmentSets",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["shipping_option", "shipping_options"],
|
||||
args: {
|
||||
entity: ShippingOption.name,
|
||||
methodSuffix: "ShippingOptions",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["shipping_profile", "shipping_profiles"],
|
||||
args: {
|
||||
entity: ShippingProfile.name,
|
||||
methodSuffix: "ShippingProfiles",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["fulfillment", "fulfillments"],
|
||||
args: {
|
||||
entity: Fulfillment.name,
|
||||
methodSuffix: "Fulfillments",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["fulfillment_provider", "fulfillment_providers"],
|
||||
args: {
|
||||
entity: FulfillmentProvider.name,
|
||||
methodSuffix: "FulfillmentProviders",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["service_zone", "service_zones"],
|
||||
args: {
|
||||
entity: ServiceZone.name,
|
||||
methodSuffix: "ServiceZones",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["geo_zone", "geo_zones"],
|
||||
args: {
|
||||
entity: GeoZone.name,
|
||||
methodSuffix: "GeoZones",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["shipping_option_rule", "shipping_option_rules"],
|
||||
args: {
|
||||
entity: ShippingOptionRule.name,
|
||||
methodSuffix: "ShippingOptionRules",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should return a full joiner configuration with custom aliases without method suffix", () => {
|
||||
const joinerConfig = defineJoinerConfig(Modules.FULFILLMENT, {
|
||||
alias: [
|
||||
{
|
||||
name: ["custom", "customs"],
|
||||
args: {
|
||||
entity: "Custom",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(joinerConfig).toEqual({
|
||||
serviceName: Modules.FULFILLMENT,
|
||||
primaryKeys: ["id"],
|
||||
schema: undefined,
|
||||
linkableKeys: {},
|
||||
alias: [
|
||||
{
|
||||
name: ["custom", "customs"],
|
||||
args: {
|
||||
entity: "Custom",
|
||||
methodSuffix: "Customs",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -45,35 +45,22 @@ describe("Abstract Module Service Factory", () => {
|
||||
class OtherModelMock1 {}
|
||||
class OtherModelMock2 {}
|
||||
|
||||
const abstractModuleService = MedusaService<
|
||||
const medusaService = MedusaService({
|
||||
MainModelMock,
|
||||
{
|
||||
OtherModelMock1: {
|
||||
dto: any
|
||||
singular: "OtherModelMock1"
|
||||
plural: "OtherModelMock1s"
|
||||
}
|
||||
OtherModelMock2: {
|
||||
dto: any
|
||||
singular: "OtherModelMock2"
|
||||
plural: "OtherModelMock2s"
|
||||
}
|
||||
}
|
||||
>(MainModelMock, {
|
||||
OtherModelMock1,
|
||||
OtherModelMock2,
|
||||
})
|
||||
|
||||
describe("Main Model Methods", () => {
|
||||
let instance
|
||||
let instance: medusaService
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
instance = new abstractModuleService(containerMock)
|
||||
instance = new medusaService(containerMock)
|
||||
})
|
||||
|
||||
it("should have retrieve method", async () => {
|
||||
const result = await instance.retrieve("1")
|
||||
const result = await instance.retrieveMainModelMock("1")
|
||||
expect(result).toEqual({ id: "1", name: "Item" })
|
||||
expect(containerMock.mainModelMockService.retrieve).toHaveBeenCalledWith(
|
||||
"1",
|
||||
@@ -83,7 +70,7 @@ describe("Abstract Module Service Factory", () => {
|
||||
})
|
||||
|
||||
it("should have list method", async () => {
|
||||
const result = await instance.list()
|
||||
const result = await instance.listMainModelMocks()
|
||||
expect(result).toEqual([{ id: "1", name: "Item" }])
|
||||
expect(containerMock.mainModelMockService.list).toHaveBeenCalledWith(
|
||||
{},
|
||||
@@ -93,7 +80,7 @@ describe("Abstract Module Service Factory", () => {
|
||||
})
|
||||
|
||||
it("should have delete method", async () => {
|
||||
await instance.delete("1")
|
||||
await instance.deleteMainModelMocks("1")
|
||||
expect(containerMock.mainModelMockService.delete).toHaveBeenCalledWith(
|
||||
["1"],
|
||||
defaultTransactionContext
|
||||
@@ -101,7 +88,7 @@ describe("Abstract Module Service Factory", () => {
|
||||
})
|
||||
|
||||
it("should have softDelete method", async () => {
|
||||
const result = await instance.softDelete("1")
|
||||
const result = await instance.softDeleteMainModelMocks("1")
|
||||
expect(result).toEqual({})
|
||||
expect(
|
||||
containerMock.mainModelMockService.softDelete
|
||||
@@ -109,7 +96,7 @@ describe("Abstract Module Service Factory", () => {
|
||||
})
|
||||
|
||||
it("should have restore method", async () => {
|
||||
const result = await instance.restore("1")
|
||||
const result = await instance.restoreMainModelMocks("1")
|
||||
expect(result).toEqual({})
|
||||
expect(containerMock.mainModelMockService.restore).toHaveBeenCalledWith(
|
||||
["1"],
|
||||
@@ -118,7 +105,7 @@ describe("Abstract Module Service Factory", () => {
|
||||
})
|
||||
|
||||
it("should have delete method with selector", async () => {
|
||||
await instance.delete({ selector: { id: "1" } })
|
||||
await instance.deleteMainModelMocks({ selector: { id: "1" } })
|
||||
expect(containerMock.mainModelMockService.delete).toHaveBeenCalledWith(
|
||||
[{ selector: { id: "1" } }],
|
||||
defaultTransactionContext
|
||||
@@ -131,7 +118,7 @@ describe("Abstract Module Service Factory", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
instance = new abstractModuleService(containerMock)
|
||||
instance = new medusaService(containerMock)
|
||||
})
|
||||
|
||||
it("should have retrieve method for other models", async () => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
IEventBusModuleService,
|
||||
IFileModuleService,
|
||||
IFulfillmentModuleService,
|
||||
IInventoryServiceNext,
|
||||
IInventoryService,
|
||||
INotificationModuleService,
|
||||
IOrderModuleService,
|
||||
IPaymentModuleService,
|
||||
@@ -17,7 +17,7 @@ import type {
|
||||
IPromotionModuleService,
|
||||
IRegionModuleService,
|
||||
ISalesChannelModuleService,
|
||||
IStockLocationServiceNext,
|
||||
IStockLocationService,
|
||||
IStoreModuleService,
|
||||
ITaxModuleService,
|
||||
IUserModuleService,
|
||||
@@ -84,7 +84,7 @@ declare module "@medusajs/types" {
|
||||
[ModuleRegistrationName.CART]: ICartModuleService
|
||||
[ModuleRegistrationName.CUSTOMER]: ICustomerModuleService
|
||||
[ModuleRegistrationName.EVENT_BUS]: IEventBusModuleService
|
||||
[ModuleRegistrationName.INVENTORY]: IInventoryServiceNext
|
||||
[ModuleRegistrationName.INVENTORY]: IInventoryService
|
||||
[ModuleRegistrationName.PAYMENT]: IPaymentModuleService
|
||||
[ModuleRegistrationName.PRICING]: IPricingModuleService
|
||||
[ModuleRegistrationName.PRODUCT]: IProductModuleService
|
||||
@@ -92,7 +92,7 @@ declare module "@medusajs/types" {
|
||||
[ModuleRegistrationName.SALES_CHANNEL]: ISalesChannelModuleService
|
||||
[ModuleRegistrationName.TAX]: ITaxModuleService
|
||||
[ModuleRegistrationName.FULFILLMENT]: IFulfillmentModuleService
|
||||
[ModuleRegistrationName.STOCK_LOCATION]: IStockLocationServiceNext
|
||||
[ModuleRegistrationName.STOCK_LOCATION]: IStockLocationService
|
||||
[ModuleRegistrationName.USER]: IUserModuleService
|
||||
[ModuleRegistrationName.WORKFLOW_ENGINE]: IWorkflowEngineService
|
||||
[ModuleRegistrationName.REGION]: IRegionModuleService
|
||||
|
||||
@@ -10,3 +10,4 @@ export * from "./medusa-internal-service"
|
||||
export * from "./medusa-service"
|
||||
export * from "./definition"
|
||||
export * from "./event-builder-factory"
|
||||
export * from "./joiner-config-builder"
|
||||
|
||||
147
packages/core/utils/src/modules-sdk/joiner-config-builder.ts
Normal file
147
packages/core/utils/src/modules-sdk/joiner-config-builder.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import {
|
||||
camelToSnakeCase,
|
||||
deduplicate,
|
||||
getCallerFilePath,
|
||||
MapToConfig,
|
||||
pluralize,
|
||||
upperCaseFirst,
|
||||
} from "../common"
|
||||
import { join } from "path"
|
||||
import { readdirSync, statSync } from "fs"
|
||||
|
||||
/**
|
||||
* Define joiner config for a module based on the models (object representation or entities) present in the models directory. This action will be sync until
|
||||
* we move to at least es2022 to have access to top-leve await.
|
||||
*
|
||||
* The aliases will be built from the entityQueryingConfig and custom aliases if provided, in case of aliases provided if the methodSuffix is not provided
|
||||
* then it will be inferred from the entity name of the alias args.
|
||||
*
|
||||
* @param moduleName
|
||||
* @param alias
|
||||
* @param schema
|
||||
* @param entityQueryingConfig
|
||||
* @param linkableKeys
|
||||
* @param primaryKeys
|
||||
*/
|
||||
export function defineJoinerConfig(
|
||||
moduleName: string,
|
||||
{
|
||||
alias,
|
||||
schema,
|
||||
entityQueryingConfig,
|
||||
linkableKeys,
|
||||
primaryKeys,
|
||||
}: {
|
||||
alias?: ModuleJoinerConfig["alias"]
|
||||
schema?: string
|
||||
entityQueryingConfig?: { name: string }[]
|
||||
linkableKeys?: Record<string, string>
|
||||
primaryKeys?: string[]
|
||||
} = {}
|
||||
): Omit<
|
||||
ModuleJoinerConfig,
|
||||
"serviceName" | "primaryKeys" | "linkableKeys" | "alias"
|
||||
> &
|
||||
Required<
|
||||
Pick<
|
||||
ModuleJoinerConfig,
|
||||
"serviceName" | "primaryKeys" | "linkableKeys" | "alias"
|
||||
>
|
||||
> {
|
||||
let basePath = getCallerFilePath()
|
||||
basePath = basePath.includes("dist")
|
||||
? basePath.split("dist")[0] + "dist"
|
||||
: basePath.split("src")[0] + "src"
|
||||
basePath = join(basePath, "models")
|
||||
|
||||
const models = deduplicate(
|
||||
[...(entityQueryingConfig ?? loadModels(basePath))].flatMap((v) => v!.name)
|
||||
).map((name) => ({ name }))
|
||||
|
||||
return {
|
||||
serviceName: moduleName,
|
||||
primaryKeys: primaryKeys ?? ["id"],
|
||||
schema,
|
||||
linkableKeys:
|
||||
linkableKeys ??
|
||||
models.reduce((acc, entity) => {
|
||||
acc[`${camelToSnakeCase(entity.name).toLowerCase()}_id`] = entity.name
|
||||
return acc
|
||||
}, {} as Record<string, string>),
|
||||
alias: [
|
||||
...[...(alias ?? ([] as any))].map((alias) => ({
|
||||
name: alias.name,
|
||||
args: {
|
||||
entity: alias.args.entity,
|
||||
methodSuffix:
|
||||
alias.args.methodSuffix ??
|
||||
pluralize(upperCaseFirst(alias.args.entity)),
|
||||
},
|
||||
})),
|
||||
...models.map((entity, i) => ({
|
||||
name: [
|
||||
`${camelToSnakeCase(entity.name).toLowerCase()}`,
|
||||
`${pluralize(camelToSnakeCase(entity.name).toLowerCase())}`,
|
||||
],
|
||||
args: {
|
||||
entity: entity.name,
|
||||
methodSuffix: pluralize(upperCaseFirst(entity.name)),
|
||||
},
|
||||
})),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build entities name to linkable keys map
|
||||
* @param linkableKeys
|
||||
*/
|
||||
export function buildEntitiesNameToLinkableKeysMap(
|
||||
linkableKeys: Record<string, string>
|
||||
): MapToConfig {
|
||||
const entityLinkableKeysMap: MapToConfig = {}
|
||||
Object.entries(linkableKeys).forEach(([key, value]) => {
|
||||
entityLinkableKeysMap[value] ??= []
|
||||
entityLinkableKeysMap[value].push({
|
||||
mapTo: key,
|
||||
valueFrom: key.split("_").pop()!,
|
||||
})
|
||||
})
|
||||
|
||||
return entityLinkableKeysMap
|
||||
}
|
||||
|
||||
function loadModels(basePath: string) {
|
||||
const excludedExtensions = [".ts.map", ".js.map", ".d.ts"]
|
||||
|
||||
let modelsFiles: any[] = []
|
||||
try {
|
||||
modelsFiles = readdirSync(basePath)
|
||||
} catch (e) {}
|
||||
|
||||
return modelsFiles
|
||||
.flatMap((file) => {
|
||||
if (
|
||||
file.startsWith("index.") ||
|
||||
excludedExtensions.some((ext) => file.endsWith(ext))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const filePath = join(basePath, file)
|
||||
const stats = statSync(filePath)
|
||||
|
||||
if (stats.isFile()) {
|
||||
try {
|
||||
const required = require(filePath)
|
||||
return Object.values(required).filter(
|
||||
(resource) => typeof resource === "function" && !!resource.name
|
||||
)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
.filter(Boolean) as { name: string }[]
|
||||
}
|
||||
@@ -50,93 +50,60 @@ type ModelDTOConfig = {
|
||||
update?: any
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
singular?: string
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
plural?: string
|
||||
}
|
||||
|
||||
type EntitiesConfigTemplate = { [key: string]: ModelDTOConfig }
|
||||
|
||||
type ModelConfigurationToDto<T extends ModelConfiguration> =
|
||||
T extends abstract new (...args: any) => infer R
|
||||
? R
|
||||
: T extends { dto: infer DTO }
|
||||
? DTO
|
||||
: any
|
||||
|
||||
type ModelConfigurationsToConfigTemplate<
|
||||
T extends Record<string, ModelConfiguration>
|
||||
> = {
|
||||
type ModelConfigurationsToConfigTemplate<T extends TEntityEntries> = {
|
||||
[Key in keyof T as `${Capitalize<Key & string>}`]: {
|
||||
dto: ModelConfigurationToDto<T[Key]>
|
||||
dto: T[Key] extends Constructor<any> ? InstanceType<T[Key]> : any
|
||||
create: any
|
||||
update: any
|
||||
singular: T[Key] extends { singular: string } ? T[Key]["singular"] : string
|
||||
plural: T[Key] extends { plural: string } ? T[Key]["plural"] : string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated should all notion of singular and plural be removed once all modules are aligned with the convention
|
||||
*/
|
||||
type ExtractSingularName<T extends Record<any, any>, K = keyof T> = Capitalize<
|
||||
T[K] extends { singular?: string } ? T[K]["singular"] & string : K & string
|
||||
>
|
||||
|
||||
/**
|
||||
* @deprecated should all notion of singular and plural be removed once all modules are aligned with the convention
|
||||
* The pluralize will move to where it should be used instead
|
||||
*/
|
||||
type ExtractPluralName<T extends Record<any, any>, K = keyof T> = T[K] extends {
|
||||
plural?: string
|
||||
}
|
||||
? T[K]["plural"] & string
|
||||
: Pluralize<K & string>
|
||||
|
||||
// TODO: this will be removed in the follow up pr once the main entity concept will be removed
|
||||
type ModelConfiguration = Constructor<any> | ModelDTOConfig | any
|
||||
// TODO: The future expected entry will be a DML object but in the meantime we have to maintain backward compatibility for ouw own modules and therefore we need to support Constructor<any> as well as this temporary object
|
||||
type TEntityEntries<Keys = string> = Record<
|
||||
Keys & string,
|
||||
Constructor<any> | { name?: string; singular?: string; plural?: string }
|
||||
>
|
||||
|
||||
type ExtractMutationDtoOrAny<T> = T extends unknown ? any : T
|
||||
|
||||
export interface AbstractModuleServiceBase<TEntryEntityConfig> {
|
||||
new (container: Record<any, any>, ...args: any[]): this
|
||||
|
||||
get __container__(): Record<any, any>
|
||||
|
||||
retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<any>,
|
||||
sharedContext?: Context
|
||||
): Promise<TEntryEntityConfig>
|
||||
|
||||
list(
|
||||
filters?: any,
|
||||
config?: FindConfig<any>,
|
||||
sharedContext?: Context
|
||||
): Promise<TEntryEntityConfig[]>
|
||||
|
||||
listAndCount(
|
||||
filters?: any,
|
||||
config?: FindConfig<any>,
|
||||
sharedContext?: Context
|
||||
): Promise<[TEntryEntityConfig[], number]>
|
||||
|
||||
delete(
|
||||
primaryKeyValues: string | object | string[] | object[],
|
||||
sharedContext?: Context
|
||||
): Promise<void>
|
||||
|
||||
softDelete<TReturnableLinkableKeys extends string>(
|
||||
primaryKeyValues: string | object | string[] | object[],
|
||||
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
|
||||
restore<TReturnableLinkableKeys extends string>(
|
||||
primaryKeyValues: string | object | string[] | object[],
|
||||
config?: RestoreReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
type ExtractKeysFromConfig<EntitiesConfig> = EntitiesConfig extends {
|
||||
__empty: any
|
||||
}
|
||||
? string
|
||||
: keyof EntitiesConfig
|
||||
|
||||
export type AbstractModuleService<
|
||||
TEntryEntityConfig extends ModelConfiguration,
|
||||
TEntitiesDtoConfig extends EntitiesConfigTemplate
|
||||
> = AbstractModuleServiceBase<TEntryEntityConfig> & {
|
||||
> = {
|
||||
[TEntityName in keyof TEntitiesDtoConfig as `retrieve${ExtractSingularName<
|
||||
TEntitiesDtoConfig,
|
||||
TEntityName
|
||||
@@ -269,27 +236,21 @@ export type AbstractModuleService<
|
||||
* @internal
|
||||
*/
|
||||
function buildMethodNamesFromModel(
|
||||
model: ModelConfiguration,
|
||||
suffixed: boolean = true
|
||||
modelName: string,
|
||||
model: TEntityEntries[keyof TEntityEntries]
|
||||
): Record<string, string> {
|
||||
return methods.reduce((acc, method) => {
|
||||
let modelName: string = ""
|
||||
let normalizedModelName: string = ""
|
||||
|
||||
if (method === "retrieve") {
|
||||
modelName =
|
||||
"singular" in model && model.singular
|
||||
? model.singular
|
||||
: (model as { name: string }).name
|
||||
normalizedModelName =
|
||||
"singular" in model && model.singular ? model.singular : modelName
|
||||
} else {
|
||||
modelName =
|
||||
"plural" in model && model.plural
|
||||
? model.plural
|
||||
: pluralize((model as { name: string }).name)
|
||||
normalizedModelName =
|
||||
"plural" in model && model.plural ? model.plural : pluralize(modelName)
|
||||
}
|
||||
|
||||
const methodName = suffixed
|
||||
? `${method}${upperCaseFirst(modelName)}`
|
||||
: method
|
||||
const methodName = `${method}${upperCaseFirst(normalizedModelName)}`
|
||||
|
||||
return { ...acc, [method]: methodName }
|
||||
}, {})
|
||||
@@ -300,31 +261,6 @@ function buildMethodNamesFromModel(
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* const entities = {
|
||||
* Currency,
|
||||
* Price,
|
||||
* PriceList,
|
||||
* PriceListRule,
|
||||
* PriceListRuleValue,
|
||||
* PriceRule,
|
||||
* PriceSetRuleType,
|
||||
* RuleType,
|
||||
* }
|
||||
*
|
||||
* class MyService extends ModulesSdkUtils.MedusaService<
|
||||
* PricingTypes.PriceSetDTO,
|
||||
* {
|
||||
* Currency: { dto: PricingTypes.CurrencyDTO }
|
||||
* Price: { dto: PricingTypes.PriceDTO }
|
||||
* PriceRule: { dto: PricingTypes.PriceRuleDTO }
|
||||
* RuleType: { dto: PricingTypes.RuleTypeDTO }
|
||||
* PriceList: { dto: PricingTypes.PriceListDTO }
|
||||
* PriceListRule: { dto: PricingTypes.PriceListRuleDTO }
|
||||
* }
|
||||
* >(PriceSet, entities, entityNameToLinkableKeysMap) {}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // Here the DTO's and names will be inferred from the arguments
|
||||
*
|
||||
* const entities = {
|
||||
@@ -338,26 +274,21 @@ function buildMethodNamesFromModel(
|
||||
* RuleType,
|
||||
* }
|
||||
*
|
||||
* class MyService extends ModulesSdkUtils.MedusaService(PriceSet, entities, entityNameToLinkableKeysMap) {}
|
||||
* class MyService extends ModulesSdkUtils.MedusaService(entities, entityNameToLinkableKeysMap) {}
|
||||
*
|
||||
* @param entryEntity
|
||||
* @param entities
|
||||
* @param entityNameToLinkableKeysMap
|
||||
*/
|
||||
export function MedusaService<
|
||||
TEntryEntityConfig extends ModelConfiguration = ModelConfiguration,
|
||||
EntitiesConfig extends EntitiesConfigTemplate = { __empty: any },
|
||||
TEntities extends Record<string, ModelConfiguration> = Record<
|
||||
string,
|
||||
ModelConfiguration
|
||||
>
|
||||
TEntities extends TEntityEntries<
|
||||
ExtractKeysFromConfig<EntitiesConfig>
|
||||
> = TEntityEntries<ExtractKeysFromConfig<EntitiesConfig>>
|
||||
>(
|
||||
entryEntity: (TEntryEntityConfig & { name: string }) | Constructor<any>,
|
||||
entities: TEntities,
|
||||
entityNameToLinkableKeysMap: MapToConfig = {}
|
||||
): {
|
||||
new (...args: any[]): AbstractModuleService<
|
||||
ModelConfigurationToDto<TEntryEntityConfig>,
|
||||
EntitiesConfig extends { __empty: any }
|
||||
? ModelConfigurationsToConfigTemplate<TEntities>
|
||||
: EntitiesConfig
|
||||
@@ -595,7 +526,6 @@ export function MedusaService<
|
||||
this.baseRepository_ = container.baseRepository
|
||||
|
||||
const hasEventBusModuleService = Object.keys(this.__container__).find(
|
||||
// TODO: Should use ModuleRegistrationName.EVENT_BUS but it would require to move it to the utils package to prevent circular dependencies
|
||||
(key) => key === ModuleRegistrationName.EVENT_BUS
|
||||
)
|
||||
|
||||
@@ -618,33 +548,18 @@ export function MedusaService<
|
||||
}
|
||||
}
|
||||
|
||||
const entryEntityMethods = buildMethodNamesFromModel(entryEntity, false)
|
||||
|
||||
/**
|
||||
* Build the main retrieve/list/listAndCount/delete/softDelete/restore methods for the main model
|
||||
*/
|
||||
|
||||
for (let [method, methodName] of Object.entries(entryEntityMethods)) {
|
||||
buildAndAssignMethodImpl(
|
||||
AbstractModuleService_.prototype,
|
||||
method,
|
||||
methodName,
|
||||
entryEntity.name
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the retrieve/list/listAndCount/delete/softDelete/restore methods for all the other models
|
||||
*/
|
||||
|
||||
const entitiesMethods: [
|
||||
string,
|
||||
ModelConfiguration,
|
||||
TEntities[keyof TEntities],
|
||||
Record<string, string>
|
||||
][] = Object.entries(entities).map(([name, config]) => [
|
||||
name,
|
||||
config,
|
||||
buildMethodNamesFromModel(config),
|
||||
config as TEntities[keyof TEntities],
|
||||
buildMethodNamesFromModel(name, config as TEntities[keyof TEntities]),
|
||||
])
|
||||
|
||||
for (let [modelName, model, modelsMethods] of entitiesMethods) {
|
||||
|
||||
Reference in New Issue
Block a user