feat: define link util (#7931)
* feat: define link util * handle pluralized fieldAlias for isList * serviceName ar reference * finalize * todo * WIP * finalize * fix tests * update typings * fix Module * linkable * update errors
This commit is contained in:
1 parent
9b1688944e
commit
41c4307fc7
26 files changed
+511
-329
No files matched your search
@@ -0,0 +1,92 @@
|
|||||||
|
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||||
|
import CurrencyModule from "@medusajs/currency"
|
||||||
|
import RegionModule from "@medusajs/region"
|
||||||
|
import { defineLink } from "@medusajs/utils"
|
||||||
|
import { MedusaModule } from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
|
medusaIntegrationTestRunner({
|
||||||
|
testSuite: ({ getContainer }) => {
|
||||||
|
describe("defineLink", () => {
|
||||||
|
let appContainer
|
||||||
|
let remoteQuery
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
appContainer = getContainer()
|
||||||
|
remoteQuery = appContainer.resolve("remoteQuery")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should generate a proper link definition", async () => {
|
||||||
|
const currencyLinks = CurrencyModule.linkable
|
||||||
|
const regionLinks = RegionModule.linkable
|
||||||
|
|
||||||
|
const link = defineLink(currencyLinks.currency, regionLinks.region)
|
||||||
|
|
||||||
|
const linkDefinition = MedusaModule.getCustomLinks()
|
||||||
|
.map((linkDefinition: any) => {
|
||||||
|
const definition = linkDefinition(MedusaModule.getLoadedModules())
|
||||||
|
return definition.serviceName === link.serviceName && definition
|
||||||
|
})
|
||||||
|
.filter(Boolean)[0]
|
||||||
|
|
||||||
|
expect(link.serviceName).toEqual("currencyCurrencyRegionRegionLink")
|
||||||
|
expect(linkDefinition).toEqual({
|
||||||
|
serviceName: "currencyCurrencyRegionRegionLink",
|
||||||
|
isLink: true,
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: ["currency_region"],
|
||||||
|
args: {
|
||||||
|
entity: "LinkCurrencyCurrencyRegionRegion",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primaryKeys: ["id", "currency_code", "region_id"],
|
||||||
|
relationships: [
|
||||||
|
{
|
||||||
|
serviceName: "currency",
|
||||||
|
primaryKey: "code",
|
||||||
|
foreignKey: "currency_code",
|
||||||
|
alias: "currency",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: "region",
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "region_id",
|
||||||
|
alias: "region",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
{
|
||||||
|
serviceName: "currency",
|
||||||
|
fieldAlias: {
|
||||||
|
region: "region_link.region",
|
||||||
|
},
|
||||||
|
relationship: {
|
||||||
|
serviceName: "currencyCurrencyRegionRegionLink",
|
||||||
|
primaryKey: "region_id",
|
||||||
|
foreignKey: "id",
|
||||||
|
alias: "region_link",
|
||||||
|
isList: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: "region",
|
||||||
|
fieldAlias: {
|
||||||
|
currency: "currency_link.currency",
|
||||||
|
},
|
||||||
|
relationship: {
|
||||||
|
serviceName: "currencyCurrencyRegionRegionLink",
|
||||||
|
primaryKey: "currency_code",
|
||||||
|
foreignKey: "code",
|
||||||
|
alias: "currency_link",
|
||||||
|
isList: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
ModulesSdkUtils,
|
createPgConnection,
|
||||||
promiseAll,
|
promiseAll,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export async function initModules({
|
|||||||
|
|
||||||
let shouldDestroyConnectionAutomatically = !sharedPgConnection
|
let shouldDestroyConnectionAutomatically = !sharedPgConnection
|
||||||
if (!sharedPgConnection) {
|
if (!sharedPgConnection) {
|
||||||
sharedPgConnection = ModulesSdkUtils.createPgConnection({
|
sharedPgConnection = createPgConnection({
|
||||||
clientUrl: databaseConfig.clientUrl,
|
clientUrl: databaseConfig.clientUrl,
|
||||||
schema: databaseConfig.schema,
|
schema: databaseConfig.schema,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,4 +6,3 @@ export * from "./medusa-module"
|
|||||||
export * from "./remote-link"
|
export * from "./remote-link"
|
||||||
export * from "./remote-query"
|
export * from "./remote-query"
|
||||||
export * from "./types"
|
export * from "./types"
|
||||||
export * from "./utils/define-link"
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
import { LinkModulesExtraFields, ModuleJoinerConfig } from "@medusajs/types"
|
|
||||||
import {
|
|
||||||
composeLinkName,
|
|
||||||
isObject,
|
|
||||||
isString,
|
|
||||||
toPascalCase,
|
|
||||||
} from "@medusajs/utils"
|
|
||||||
import { MedusaModule } from "../medusa-module"
|
|
||||||
|
|
||||||
type ModuleLinkableKeyConfig = {
|
|
||||||
module: string
|
|
||||||
key: string
|
|
||||||
isList?: boolean
|
|
||||||
alias?: string
|
|
||||||
shortcuts?: {
|
|
||||||
[key: string]: string | { path: string; isList?: boolean }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function defineLink(
|
|
||||||
serviceAAndKey: string | ModuleLinkableKeyConfig,
|
|
||||||
serviceBAndKey: string | ModuleLinkableKeyConfig,
|
|
||||||
options?: {
|
|
||||||
pk?: {
|
|
||||||
[key: string]: string
|
|
||||||
}
|
|
||||||
database?: {
|
|
||||||
table: string
|
|
||||||
idPrefix?: string
|
|
||||||
extraColumns?: LinkModulesExtraFields
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const register = function (
|
|
||||||
modules: ModuleJoinerConfig[]
|
|
||||||
): ModuleJoinerConfig {
|
|
||||||
let serviceA: string
|
|
||||||
let serviceAKey: string
|
|
||||||
let serviceAIsList = false
|
|
||||||
let serviceAObj: Partial<ModuleLinkableKeyConfig> = {}
|
|
||||||
|
|
||||||
let serviceB: string
|
|
||||||
let serviceBKey: string
|
|
||||||
let serviceBIsList = false
|
|
||||||
let serviceBObj: Partial<ModuleLinkableKeyConfig> = {}
|
|
||||||
|
|
||||||
if (isString(serviceAAndKey)) {
|
|
||||||
let [mod, key] = (serviceAAndKey as string).split(".")
|
|
||||||
serviceA = mod
|
|
||||||
if (key.endsWith("[]")) {
|
|
||||||
serviceAIsList = true
|
|
||||||
key = key.slice(0, -2)
|
|
||||||
}
|
|
||||||
serviceAKey = key
|
|
||||||
} else if (isObject(serviceAAndKey)) {
|
|
||||||
const objA = serviceAAndKey as ModuleLinkableKeyConfig
|
|
||||||
serviceAObj = objA
|
|
||||||
|
|
||||||
serviceA = objA.module
|
|
||||||
serviceAKey = objA.key
|
|
||||||
serviceAIsList = !!objA.isList
|
|
||||||
} else {
|
|
||||||
throw new Error("Invalid value for serviceA config")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isString(serviceBAndKey)) {
|
|
||||||
let [mod, key] = (serviceBAndKey as string).split(".")
|
|
||||||
serviceB = mod
|
|
||||||
if (key.endsWith("[]")) {
|
|
||||||
serviceBIsList = true
|
|
||||||
key = key.slice(0, -2)
|
|
||||||
}
|
|
||||||
serviceBKey = key
|
|
||||||
} else if (isObject(serviceBAndKey)) {
|
|
||||||
const objB = serviceBAndKey as ModuleLinkableKeyConfig
|
|
||||||
serviceBObj = objB
|
|
||||||
|
|
||||||
serviceB = objB.module
|
|
||||||
serviceBKey = objB.key
|
|
||||||
serviceBIsList = !!objB.isList
|
|
||||||
} else {
|
|
||||||
throw new Error("Invalid value for serviceB config")
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceAInfo = modules.find((mod) => mod.serviceName === serviceA)
|
|
||||||
const serviceBInfo = modules.find((mod) => mod.serviceName === serviceB)
|
|
||||||
if (!serviceAInfo) {
|
|
||||||
throw new Error(`Service ${serviceA} was not found`)
|
|
||||||
}
|
|
||||||
if (!serviceBInfo) {
|
|
||||||
throw new Error(`Service ${serviceB} was not found`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceAKeyInfo = serviceAInfo.linkableKeys?.[serviceAKey]
|
|
||||||
const serviceBKeyInfo = serviceBInfo.linkableKeys?.[serviceBKey]
|
|
||||||
if (!serviceAKeyInfo) {
|
|
||||||
throw new Error(
|
|
||||||
`Key ${serviceAKey} is not linkable on service ${serviceA}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (!serviceBKeyInfo) {
|
|
||||||
throw new Error(
|
|
||||||
`Key ${serviceBKey} is not linkable on service ${serviceB}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let serviceAAliases = serviceAInfo.alias ?? []
|
|
||||||
if (!Array.isArray(serviceAAliases)) {
|
|
||||||
serviceAAliases = [serviceAAliases]
|
|
||||||
}
|
|
||||||
|
|
||||||
let aliasAOptions =
|
|
||||||
serviceAObj.alias ??
|
|
||||||
serviceAAliases.find((a) => {
|
|
||||||
return a.args?.entity == serviceAKeyInfo
|
|
||||||
})?.name
|
|
||||||
|
|
||||||
let aliasA = ""
|
|
||||||
if (Array.isArray(aliasAOptions)) {
|
|
||||||
aliasA = aliasAOptions[0]
|
|
||||||
}
|
|
||||||
if (!aliasA) {
|
|
||||||
throw new Error(
|
|
||||||
`You need to provide an alias for ${serviceA}.${serviceAKey}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let serviceBAliases = serviceBInfo.alias ?? []
|
|
||||||
if (!Array.isArray(serviceBAliases)) {
|
|
||||||
serviceBAliases = [serviceBAliases]
|
|
||||||
}
|
|
||||||
|
|
||||||
let aliasBOptions =
|
|
||||||
serviceBObj.alias ??
|
|
||||||
serviceBAliases.find((a) => {
|
|
||||||
return a.args?.entity == serviceBKeyInfo
|
|
||||||
})?.name
|
|
||||||
|
|
||||||
let aliasB = ""
|
|
||||||
if (Array.isArray(aliasBOptions)) {
|
|
||||||
aliasB = aliasBOptions[0]
|
|
||||||
}
|
|
||||||
if (!aliasB) {
|
|
||||||
throw new Error(
|
|
||||||
`You need to provide an alias for ${serviceB}.${serviceBKey}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let serviceAPrimaryKey = options?.pk?.[serviceA] ?? serviceAInfo.primaryKeys
|
|
||||||
if (Array.isArray(serviceAPrimaryKey)) {
|
|
||||||
serviceAPrimaryKey = serviceAPrimaryKey[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
let serviceBPrimaryKey = options?.pk?.[serviceB] ?? serviceBInfo.primaryKeys
|
|
||||||
if (Array.isArray(serviceBPrimaryKey)) {
|
|
||||||
serviceBPrimaryKey = serviceBPrimaryKey[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceName = composeLinkName(serviceA, aliasA, serviceB, aliasB)
|
|
||||||
|
|
||||||
const linkDefinition: ModuleJoinerConfig = {
|
|
||||||
serviceName,
|
|
||||||
isLink: true,
|
|
||||||
alias: [
|
|
||||||
{
|
|
||||||
name: [aliasA + "_" + aliasB],
|
|
||||||
args: {
|
|
||||||
entity: toPascalCase(
|
|
||||||
["Link", serviceA, aliasA, serviceB, aliasB].join("_")
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
primaryKeys: ["id", serviceAKey, serviceBKey],
|
|
||||||
relationships: [
|
|
||||||
{
|
|
||||||
serviceName: serviceA,
|
|
||||||
primaryKey: serviceAPrimaryKey!,
|
|
||||||
foreignKey: serviceAKey,
|
|
||||||
alias: aliasA,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
serviceName: serviceB,
|
|
||||||
primaryKey: serviceBPrimaryKey!,
|
|
||||||
foreignKey: serviceBKey,
|
|
||||||
alias: aliasB,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
extends: [
|
|
||||||
{
|
|
||||||
serviceName: serviceA,
|
|
||||||
fieldAlias: {
|
|
||||||
[aliasB]: aliasB + "_link." + aliasB, //plural aliasA
|
|
||||||
},
|
|
||||||
relationship: {
|
|
||||||
serviceName,
|
|
||||||
primaryKey: serviceAKey,
|
|
||||||
foreignKey: serviceBPrimaryKey!,
|
|
||||||
alias: aliasB + "_link", // plural alias
|
|
||||||
isList: serviceAIsList,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
serviceName: serviceB,
|
|
||||||
fieldAlias: {
|
|
||||||
[aliasA]: aliasA + "_link." + aliasA,
|
|
||||||
},
|
|
||||||
relationship: {
|
|
||||||
serviceName,
|
|
||||||
primaryKey: serviceBKey,
|
|
||||||
foreignKey: serviceAPrimaryKey!,
|
|
||||||
alias: aliasA + "_link", // plural alias
|
|
||||||
isList: serviceBIsList,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.database) {
|
|
||||||
const { table, idPrefix, extraColumns } = options.database
|
|
||||||
linkDefinition.databaseConfig = {
|
|
||||||
tableName: table,
|
|
||||||
idPrefix,
|
|
||||||
extraFields: extraColumns,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return linkDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
MedusaModule.setCustomLink(register)
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
export * from "./clean-graphql-schema"
|
export * from "./clean-graphql-schema"
|
||||||
export * from "./define-link"
|
|
||||||
export * from "./graphql-schema-to-fields"
|
export * from "./graphql-schema-to-fields"
|
||||||
@@ -19,11 +19,11 @@ export type IDmlEntityConfig = string | { name?: string; tableName: string }
|
|||||||
|
|
||||||
export type InferDmlEntityNameFromConfig<TConfig extends IDmlEntityConfig> =
|
export type InferDmlEntityNameFromConfig<TConfig extends IDmlEntityConfig> =
|
||||||
TConfig extends string
|
TConfig extends string
|
||||||
? Capitalize<CamelCase<TConfig>>
|
? CamelCase<TConfig>
|
||||||
: TConfig extends { name: string }
|
: TConfig extends { name: string }
|
||||||
? Capitalize<CamelCase<TConfig["name"] & string>>
|
? CamelCase<TConfig["name"]>
|
||||||
: TConfig extends { tableName: string }
|
: TConfig extends { tableName: string }
|
||||||
? Capitalize<CamelCase<TConfig["tableName"] & string>>
|
? CamelCase<TConfig["tableName"]>
|
||||||
: never
|
: never
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ConfigModule } from "@medusajs/types"
|
import { ConfigModule } from "@medusajs/types"
|
||||||
import { Modules } from "../modules-sdk"
|
import { Modules } from "../modules-sdk/definition"
|
||||||
|
|
||||||
const DEFAULT_SECRET = "supersecret"
|
const DEFAULT_SECRET = "supersecret"
|
||||||
const DEFAULT_ADMIN_URL = "http://localhost:9000"
|
const DEFAULT_ADMIN_URL = "http://localhost:9000"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export * from "./deduplicate"
|
|||||||
export * from "./deep-copy"
|
export * from "./deep-copy"
|
||||||
export * from "./deep-equal-obj"
|
export * from "./deep-equal-obj"
|
||||||
export * from "./deep-flat-map"
|
export * from "./deep-flat-map"
|
||||||
export * from "./define-config"
|
|
||||||
export * from "./errors"
|
export * from "./errors"
|
||||||
export * from "./file-system"
|
export * from "./file-system"
|
||||||
export * from "./generate-entity-id"
|
export * from "./generate-entity-id"
|
||||||
@@ -67,3 +66,4 @@ export * from "./trim-zeros"
|
|||||||
export * from "./upper-case-first"
|
export * from "./upper-case-first"
|
||||||
export * from "./validate-handle"
|
export * from "./validate-handle"
|
||||||
export * from "./wrap-handler"
|
export * from "./wrap-handler"
|
||||||
|
export * from "./define-config"
|
||||||
@@ -72,7 +72,7 @@ describe("Entity builder", () => {
|
|||||||
phones: model.array(),
|
phones: model.array(),
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(user.name).toEqual("User")
|
expect(user.name).toEqual("user")
|
||||||
expect(user.parse().tableName).toEqual("user")
|
expect(user.parse().tableName).toEqual("user")
|
||||||
|
|
||||||
const User = toMikroORMEntity(user)
|
const User = toMikroORMEntity(user)
|
||||||
@@ -202,7 +202,7 @@ describe("Entity builder", () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(user.name).toEqual("User")
|
expect(user.name).toEqual("user")
|
||||||
expect(user.parse().tableName).toEqual("user_table")
|
expect(user.parse().tableName).toEqual("user_table")
|
||||||
|
|
||||||
const User = toMikroORMEntity(user)
|
const User = toMikroORMEntity(user)
|
||||||
@@ -324,7 +324,7 @@ describe("Entity builder", () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(user.name).toEqual("UserRole")
|
expect(user.name).toEqual("userRole")
|
||||||
expect(user.parse().tableName).toEqual("user_role")
|
expect(user.parse().tableName).toEqual("user_role")
|
||||||
|
|
||||||
const User = toMikroORMEntity(user)
|
const User = toMikroORMEntity(user)
|
||||||
@@ -3849,7 +3849,7 @@ describe("Entity builder", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(defineEmail).toThrow(
|
expect(defineEmail).toThrow(
|
||||||
'Cannot cascade delete "user" relationship(s) from "Email" entity. Child to parent cascades are not allowed'
|
'Cannot cascade delete "user" relationship(s) from "email" entity. Child to parent cascades are not allowed'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export class EntityBuilder {
|
|||||||
*
|
*
|
||||||
* export default MyCustom
|
* export default MyCustom
|
||||||
*/
|
*/
|
||||||
define<Schema extends DMLSchema, TConfig extends IDmlEntityConfig>(
|
define<Schema extends DMLSchema, const TConfig extends IDmlEntityConfig>(
|
||||||
nameOrConfig: TConfig,
|
nameOrConfig: TConfig,
|
||||||
schema: Schema
|
schema: Schema
|
||||||
): DmlEntity<
|
): DmlEntity<
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import {
|
|||||||
IsDmlEntity,
|
IsDmlEntity,
|
||||||
QueryCondition,
|
QueryCondition,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { isObject, isString, toCamelCase, upperCaseFirst } from "../common"
|
import { isObject, isString, toCamelCase } from "../common"
|
||||||
import { transformIndexWhere } from "./helpers/entity-builder/build-indexes"
|
import { transformIndexWhere } from "./helpers/entity-builder/build-indexes"
|
||||||
import { BelongsTo } from "./relations/belongs-to"
|
import { BelongsTo } from "./relations/belongs-to"
|
||||||
|
|
||||||
function extractNameAndTableName<Config extends IDmlEntityConfig>(
|
function extractNameAndTableName<const Config extends IDmlEntityConfig>(
|
||||||
nameOrConfig: Config
|
nameOrConfig: Config
|
||||||
) {
|
) {
|
||||||
const result = {
|
const result = {
|
||||||
@@ -27,9 +27,8 @@ function extractNameAndTableName<Config extends IDmlEntityConfig>(
|
|||||||
if (isString(nameOrConfig)) {
|
if (isString(nameOrConfig)) {
|
||||||
const [schema, ...rest] = nameOrConfig.split(".")
|
const [schema, ...rest] = nameOrConfig.split(".")
|
||||||
const name = rest.length ? rest.join(".") : schema
|
const name = rest.length ? rest.join(".") : schema
|
||||||
result.name = upperCaseFirst(
|
result.name = toCamelCase(name) as InferDmlEntityNameFromConfig<Config>
|
||||||
toCamelCase(name)
|
|
||||||
) as InferDmlEntityNameFromConfig<Config>
|
|
||||||
result.tableName = nameOrConfig
|
result.tableName = nameOrConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +43,7 @@ function extractNameAndTableName<Config extends IDmlEntityConfig>(
|
|||||||
const [schema, ...rest] = potentialName.split(".")
|
const [schema, ...rest] = potentialName.split(".")
|
||||||
const name = rest.length ? rest.join(".") : schema
|
const name = rest.length ? rest.join(".") : schema
|
||||||
|
|
||||||
result.name = upperCaseFirst(
|
result.name = toCamelCase(name) as InferDmlEntityNameFromConfig<Config>
|
||||||
toCamelCase(name)
|
|
||||||
) as InferDmlEntityNameFromConfig<Config>
|
|
||||||
result.tableName = nameOrConfig.tableName
|
result.tableName = nameOrConfig.tableName
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +56,7 @@ function extractNameAndTableName<Config extends IDmlEntityConfig>(
|
|||||||
*/
|
*/
|
||||||
export class DmlEntity<
|
export class DmlEntity<
|
||||||
Schema extends DMLSchema,
|
Schema extends DMLSchema,
|
||||||
TConfig extends IDmlEntityConfig
|
const TConfig extends IDmlEntityConfig
|
||||||
> implements IDmlEntity<Schema, TConfig>
|
> implements IDmlEntity<Schema, TConfig>
|
||||||
{
|
{
|
||||||
[IsDmlEntity]: true = true
|
[IsDmlEntity]: true = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
export * from "./common"
|
||||||
export * from "./api-key"
|
export * from "./api-key"
|
||||||
export * from "./auth"
|
export * from "./auth"
|
||||||
export * from "./bundles"
|
export * from "./bundles"
|
||||||
export * from "./common"
|
|
||||||
export * from "./dal"
|
export * from "./dal"
|
||||||
export * from "./decorators"
|
export * from "./decorators"
|
||||||
export * from "./defaults"
|
export * from "./defaults"
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
ShippingOptionRule,
|
ShippingOptionRule,
|
||||||
ShippingProfile,
|
ShippingProfile,
|
||||||
} from "../__fixtures__/joiner-config/entities"
|
} from "../__fixtures__/joiner-config/entities"
|
||||||
|
import { upperCaseFirst } from "../../common"
|
||||||
|
|
||||||
describe("joiner-config-builder", () => {
|
describe("joiner-config-builder", () => {
|
||||||
describe("defineJoiner | Mikro orm objects", () => {
|
describe("defineJoiner | Mikro orm objects", () => {
|
||||||
@@ -420,14 +421,15 @@ describe("joiner-config-builder", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const linkableKeys = buildLinkableKeysFromDmlObjects([user, car])
|
const linkableKeys = buildLinkableKeysFromDmlObjects([user, car])
|
||||||
|
|
||||||
expectTypeOf(linkableKeys).toMatchTypeOf<{
|
expectTypeOf(linkableKeys).toMatchTypeOf<{
|
||||||
user_id: "User"
|
user_id: "User"
|
||||||
car_number_plate: "Car"
|
car_number_plate: "Car"
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
expect(linkableKeys).toEqual({
|
expect(linkableKeys).toEqual({
|
||||||
user_id: user.name,
|
user_id: upperCaseFirst(user.name),
|
||||||
car_number_plate: car.name,
|
car_number_plate: upperCaseFirst(car.name),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -453,12 +455,18 @@ describe("joiner-config-builder", () => {
|
|||||||
name: model.text(),
|
name: model.text(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const car = model.define("car", {
|
const car = model.define(
|
||||||
|
{ name: "car", tableName: "car" },
|
||||||
|
{
|
||||||
id: model.id(),
|
id: model.id(),
|
||||||
number_plate: model.text().primaryKey(),
|
number_plate: model.text().primaryKey(),
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const linkConfig = buildLinkConfigFromDmlObjects("myService", [user, car])
|
const linkConfig = buildLinkConfigFromDmlObjects("myService", {
|
||||||
|
user,
|
||||||
|
car,
|
||||||
|
})
|
||||||
|
|
||||||
expectTypeOf(linkConfig).toMatchTypeOf<{
|
expectTypeOf(linkConfig).toMatchTypeOf<{
|
||||||
user: {
|
user: {
|
||||||
|
|||||||
@@ -0,0 +1,316 @@
|
|||||||
|
import { LinkModulesExtraFields, ModuleJoinerConfig } from "@medusajs/types"
|
||||||
|
import { isObject, pluralize, toPascalCase } from "../common"
|
||||||
|
import { composeLinkName } from "../link"
|
||||||
|
|
||||||
|
type InputSource = {
|
||||||
|
serviceName: string
|
||||||
|
field: string
|
||||||
|
linkable: string
|
||||||
|
primaryKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputToJson = {
|
||||||
|
toJSON: () => InputSource
|
||||||
|
}
|
||||||
|
|
||||||
|
type CombinedSource = Record<any, any> & InputToJson
|
||||||
|
|
||||||
|
type InputOptions = {
|
||||||
|
source: CombinedSource | InputSource
|
||||||
|
isList?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtraOptions = {
|
||||||
|
pk?: {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
database?: {
|
||||||
|
table: string
|
||||||
|
idPrefix?: string
|
||||||
|
extraColumns?: LinkModulesExtraFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefineLinkInputSource = InputSource | InputOptions | CombinedSource
|
||||||
|
|
||||||
|
type ModuleLinkableKeyConfig = {
|
||||||
|
module: string
|
||||||
|
key: string
|
||||||
|
isList?: boolean
|
||||||
|
primaryKey: string
|
||||||
|
alias: string
|
||||||
|
shortcuts?: {
|
||||||
|
[key: string]: string | { path: string; isList?: boolean }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInputOptions(input: any): input is InputOptions {
|
||||||
|
return isObject(input) && "source" in input
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInputSource(input: any): input is InputSource {
|
||||||
|
return (isObject(input) && "serviceName" in input) || "toJSON" in input
|
||||||
|
}
|
||||||
|
|
||||||
|
function isToJSON(input: any): input is InputToJson {
|
||||||
|
return isObject(input) && "toJSON" in input
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defineLink(
|
||||||
|
leftService: DefineLinkInputSource,
|
||||||
|
rightService: DefineLinkInputSource,
|
||||||
|
linkServiceOptions?: ExtraOptions
|
||||||
|
) {
|
||||||
|
let serviceAObj = {} as ModuleLinkableKeyConfig
|
||||||
|
let serviceBObj = {} as ModuleLinkableKeyConfig
|
||||||
|
|
||||||
|
if (isInputSource(leftService)) {
|
||||||
|
const source = isToJSON(leftService) ? leftService.toJSON() : leftService
|
||||||
|
|
||||||
|
serviceAObj = {
|
||||||
|
key: source.linkable,
|
||||||
|
alias: source.field,
|
||||||
|
primaryKey: source.primaryKey,
|
||||||
|
isList: false,
|
||||||
|
module: source.serviceName,
|
||||||
|
}
|
||||||
|
} else if (isInputOptions(leftService)) {
|
||||||
|
const source = isToJSON(leftService.source)
|
||||||
|
? leftService.source.toJSON()
|
||||||
|
: leftService.source
|
||||||
|
|
||||||
|
serviceAObj = {
|
||||||
|
key: source.linkable,
|
||||||
|
alias: source.field,
|
||||||
|
primaryKey: source.primaryKey,
|
||||||
|
isList: leftService.isList ?? false,
|
||||||
|
module: source.serviceName,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error("Invalid linkable passed for the first argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInputSource(rightService)) {
|
||||||
|
const source = isToJSON(rightService) ? rightService.toJSON() : rightService
|
||||||
|
|
||||||
|
serviceBObj = {
|
||||||
|
key: source.linkable,
|
||||||
|
alias: source.field,
|
||||||
|
primaryKey: source.primaryKey,
|
||||||
|
isList: false,
|
||||||
|
module: source.serviceName,
|
||||||
|
}
|
||||||
|
} else if (isInputOptions(rightService)) {
|
||||||
|
const source = isToJSON(rightService.source)
|
||||||
|
? rightService.source.toJSON()
|
||||||
|
: rightService.source
|
||||||
|
|
||||||
|
serviceBObj = {
|
||||||
|
key: source.linkable,
|
||||||
|
alias: source.field,
|
||||||
|
primaryKey: source.primaryKey,
|
||||||
|
isList: rightService.isList ?? false,
|
||||||
|
module: source.serviceName,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Invalid linkable passed for the second argument`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = { serviceName: "" }
|
||||||
|
|
||||||
|
const register = function (
|
||||||
|
modules: ModuleJoinerConfig[]
|
||||||
|
): ModuleJoinerConfig {
|
||||||
|
const serviceAInfo = modules
|
||||||
|
.map((mod) => mod[serviceAObj.module])
|
||||||
|
.filter(Boolean)[0]
|
||||||
|
const serviceBInfo = modules
|
||||||
|
.map((mod) => mod[serviceBObj.module])
|
||||||
|
.filter(Boolean)[0]
|
||||||
|
if (!serviceAInfo) {
|
||||||
|
throw new Error(`Service ${serviceAObj.module} was not found`)
|
||||||
|
}
|
||||||
|
if (!serviceBInfo) {
|
||||||
|
throw new Error(`Service ${serviceBObj.module} was not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAKeyInfo =
|
||||||
|
serviceAInfo.__joinerConfig.linkableKeys?.[serviceAObj.key]
|
||||||
|
const serviceBKeyInfo =
|
||||||
|
serviceBInfo.__joinerConfig.linkableKeys?.[serviceBObj.key]
|
||||||
|
if (!serviceAKeyInfo) {
|
||||||
|
throw new Error(
|
||||||
|
`Key ${serviceAObj.key} is not linkable on service ${serviceAObj.module}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!serviceBKeyInfo) {
|
||||||
|
throw new Error(
|
||||||
|
`Key ${serviceBObj.key} is not linkable on service ${serviceBObj.module}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let serviceAAliases = serviceAInfo.__joinerConfig.alias ?? []
|
||||||
|
if (!Array.isArray(serviceAAliases)) {
|
||||||
|
serviceAAliases = [serviceAAliases]
|
||||||
|
}
|
||||||
|
|
||||||
|
let aliasAOptions =
|
||||||
|
serviceAObj.alias ??
|
||||||
|
serviceAAliases.find((a) => {
|
||||||
|
return a.args?.entity == serviceAKeyInfo
|
||||||
|
})?.name
|
||||||
|
|
||||||
|
let aliasA = aliasAOptions
|
||||||
|
if (Array.isArray(aliasAOptions)) {
|
||||||
|
aliasA = aliasAOptions[0]
|
||||||
|
}
|
||||||
|
if (!aliasA) {
|
||||||
|
throw new Error(
|
||||||
|
`You need to provide an alias for ${serviceAObj.module}.${serviceAObj.key}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let serviceBAliases = serviceBInfo.__joinerConfig.alias ?? []
|
||||||
|
if (!Array.isArray(serviceBAliases)) {
|
||||||
|
serviceBAliases = [serviceBAliases]
|
||||||
|
}
|
||||||
|
|
||||||
|
let aliasBOptions =
|
||||||
|
serviceBObj.alias ??
|
||||||
|
serviceBAliases.find((a) => {
|
||||||
|
return a.args?.entity == serviceBKeyInfo
|
||||||
|
})?.name
|
||||||
|
|
||||||
|
let aliasB = aliasBOptions
|
||||||
|
if (Array.isArray(aliasBOptions)) {
|
||||||
|
aliasB = aliasBOptions[0]
|
||||||
|
}
|
||||||
|
if (!aliasB) {
|
||||||
|
throw new Error(
|
||||||
|
`You need to provide an alias for ${serviceBObj.module}.${serviceBObj.key}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleAPrimaryKeys = serviceAInfo.__joinerConfig.primaryKeys
|
||||||
|
let serviceAPrimaryKey =
|
||||||
|
serviceAObj.primaryKey ??
|
||||||
|
linkServiceOptions?.pk?.[serviceAObj.module] ??
|
||||||
|
moduleAPrimaryKeys
|
||||||
|
if (Array.isArray(serviceAPrimaryKey)) {
|
||||||
|
serviceAPrimaryKey = serviceAPrimaryKey[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const isModuleAPrimaryKeyValid =
|
||||||
|
moduleAPrimaryKeys.includes(serviceAPrimaryKey)
|
||||||
|
if (!isModuleAPrimaryKeyValid) {
|
||||||
|
throw new Error(
|
||||||
|
`Primary key ${serviceAPrimaryKey} is not defined on service ${serviceAObj.module}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleBPrimaryKeys = serviceBInfo.__joinerConfig.primaryKeys
|
||||||
|
let serviceBPrimaryKey =
|
||||||
|
serviceBObj.primaryKey ??
|
||||||
|
linkServiceOptions?.pk?.[serviceBObj.module] ??
|
||||||
|
moduleBPrimaryKeys
|
||||||
|
if (Array.isArray(serviceBPrimaryKey)) {
|
||||||
|
serviceBPrimaryKey = serviceBPrimaryKey[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const isModuleBPrimaryKeyValid =
|
||||||
|
moduleBPrimaryKeys.includes(serviceBPrimaryKey)
|
||||||
|
if (!isModuleBPrimaryKeyValid) {
|
||||||
|
throw new Error(
|
||||||
|
`Primary key ${serviceBPrimaryKey} is not defined on service ${serviceBObj.module}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
output.serviceName = composeLinkName(
|
||||||
|
serviceAObj.module,
|
||||||
|
aliasA,
|
||||||
|
serviceBObj.module,
|
||||||
|
aliasB
|
||||||
|
)
|
||||||
|
|
||||||
|
const linkDefinition: ModuleJoinerConfig = {
|
||||||
|
serviceName: output.serviceName,
|
||||||
|
isLink: true,
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: [aliasA + "_" + aliasB],
|
||||||
|
args: {
|
||||||
|
entity: toPascalCase(
|
||||||
|
[
|
||||||
|
"Link",
|
||||||
|
serviceAObj.module,
|
||||||
|
aliasA,
|
||||||
|
serviceBObj.module,
|
||||||
|
aliasB,
|
||||||
|
].join("_")
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primaryKeys: ["id", serviceAObj.key, serviceBObj.key],
|
||||||
|
relationships: [
|
||||||
|
{
|
||||||
|
serviceName: serviceAObj.module,
|
||||||
|
primaryKey: serviceAPrimaryKey,
|
||||||
|
foreignKey: serviceAObj.key,
|
||||||
|
alias: aliasA,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: serviceBObj.module,
|
||||||
|
primaryKey: serviceBPrimaryKey!,
|
||||||
|
foreignKey: serviceBObj.key,
|
||||||
|
alias: aliasB,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
{
|
||||||
|
serviceName: serviceAObj.module,
|
||||||
|
fieldAlias: {
|
||||||
|
[serviceBObj.isList ? pluralize(aliasB) : aliasB]:
|
||||||
|
aliasB + "_link." + aliasB, //plural aliasA
|
||||||
|
},
|
||||||
|
relationship: {
|
||||||
|
serviceName: output.serviceName,
|
||||||
|
primaryKey: serviceBObj.key,
|
||||||
|
foreignKey: serviceBPrimaryKey,
|
||||||
|
alias: aliasB + "_link", // plural alias
|
||||||
|
isList: serviceBObj.isList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: serviceBObj.module,
|
||||||
|
fieldAlias: {
|
||||||
|
[serviceAObj.isList ? pluralize(aliasA) : aliasA]:
|
||||||
|
aliasA + "_link." + aliasA,
|
||||||
|
},
|
||||||
|
relationship: {
|
||||||
|
serviceName: output.serviceName,
|
||||||
|
primaryKey: serviceAObj.key,
|
||||||
|
foreignKey: serviceAPrimaryKey,
|
||||||
|
alias: aliasA + "_link", // plural alias
|
||||||
|
isList: serviceAObj.isList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkServiceOptions?.database) {
|
||||||
|
const { table, idPrefix, extraColumns } = linkServiceOptions.database
|
||||||
|
linkDefinition.databaseConfig = {
|
||||||
|
tableName: table,
|
||||||
|
idPrefix,
|
||||||
|
extraFields: extraColumns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return linkDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
global.MedusaModule.setCustomLink(register)
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
@@ -14,3 +14,4 @@ export * from "./medusa-service"
|
|||||||
export * from "./migration-scripts"
|
export * from "./migration-scripts"
|
||||||
export * from "./mikro-orm-cli-config-builder"
|
export * from "./mikro-orm-cli-config-builder"
|
||||||
export * from "./module"
|
export * from "./module"
|
||||||
|
export * from "./define-link"
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
IDmlEntity,
|
||||||
JoinerServiceConfigAlias,
|
JoinerServiceConfigAlias,
|
||||||
ModuleJoinerConfig,
|
ModuleJoinerConfig,
|
||||||
PropertyType,
|
PropertyType,
|
||||||
@@ -108,13 +109,14 @@ export function defineJoinerConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!primaryKeys && modelDefinitions.size) {
|
if (!primaryKeys && modelDefinitions.size) {
|
||||||
const linkConfig = buildLinkConfigFromDmlObjects(serviceName, [
|
const linkConfig = buildLinkConfigFromDmlObjects(
|
||||||
...modelDefinitions.values(),
|
serviceName,
|
||||||
])
|
Object.fromEntries(modelDefinitions)
|
||||||
|
)
|
||||||
|
|
||||||
primaryKeys = deduplicate(
|
primaryKeys = deduplicate(
|
||||||
Object.values(linkConfig).flatMap((entityLinkConfig) => {
|
Object.values(linkConfig).flatMap((entityLinkConfig) => {
|
||||||
return (Object.values(entityLinkConfig) as any[])
|
return (Object.values(entityLinkConfig as any) as any[])
|
||||||
.filter((linkableConfig) => isObject(linkableConfig))
|
.filter((linkableConfig) => isObject(linkableConfig))
|
||||||
.map((linkableConfig) => {
|
.map((linkableConfig) => {
|
||||||
return linkableConfig.primaryKey
|
return linkableConfig.primaryKey
|
||||||
@@ -152,7 +154,7 @@ export function defineJoinerConfig(
|
|||||||
`${pluralize(camelToSnakeCase(entity.name).toLowerCase())}`,
|
`${pluralize(camelToSnakeCase(entity.name).toLowerCase())}`,
|
||||||
],
|
],
|
||||||
args: {
|
args: {
|
||||||
entity: entity.name,
|
entity: upperCaseFirst(entity.name),
|
||||||
methodSuffix: pluralize(upperCaseFirst(entity.name)),
|
methodSuffix: pluralize(upperCaseFirst(entity.name)),
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
@@ -175,6 +177,8 @@ export function defineJoinerConfig(
|
|||||||
* test: model.text(),
|
* test: model.text(),
|
||||||
* })
|
* })
|
||||||
*
|
*
|
||||||
|
* const linkableKeys = buildLinkableKeysFromDmlObjects([user, car])
|
||||||
|
*
|
||||||
* // output:
|
* // output:
|
||||||
* // {
|
* // {
|
||||||
* // user_id: 'User',
|
* // user_id: 'User',
|
||||||
@@ -213,7 +217,7 @@ export function buildLinkableKeysFromDmlObjects<
|
|||||||
|
|
||||||
if (primaryKeys.length) {
|
if (primaryKeys.length) {
|
||||||
primaryKeys.forEach((primaryKey) => {
|
primaryKeys.forEach((primaryKey) => {
|
||||||
linkableKeys[primaryKey] = model.name
|
linkableKeys[primaryKey] = upperCaseFirst(model.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,7 +254,7 @@ export function buildLinkableKeysFromMikroOrmObjects(
|
|||||||
* test: model.text(),
|
* test: model.text(),
|
||||||
* })
|
* })
|
||||||
*
|
*
|
||||||
* const links = buildLinkConfigFromDmlObjects('userService', [user, car])
|
* const links = buildLinkConfigFromDmlObjects('userService', { user, car })
|
||||||
*
|
*
|
||||||
* // output:
|
* // output:
|
||||||
* // {
|
* // {
|
||||||
@@ -279,19 +283,17 @@ export function buildLinkableKeysFromMikroOrmObjects(
|
|||||||
*/
|
*/
|
||||||
export function buildLinkConfigFromDmlObjects<
|
export function buildLinkConfigFromDmlObjects<
|
||||||
const ServiceName extends string,
|
const ServiceName extends string,
|
||||||
const T extends DmlEntity<any, any>[]
|
const T extends Record<string, IDmlEntity<any, any>>
|
||||||
>(
|
>(serviceName: ServiceName, models: T): InfersLinksConfig<ServiceName, T> {
|
||||||
serviceName: ServiceName,
|
|
||||||
models: T = [] as unknown as T
|
|
||||||
): InfersLinksConfig<ServiceName, T> {
|
|
||||||
const linkConfig = {} as InfersLinksConfig<ServiceName, T>
|
const linkConfig = {} as InfersLinksConfig<ServiceName, T>
|
||||||
|
|
||||||
for (const model of models) {
|
for (const model of Object.values(models) ?? []) {
|
||||||
if (!DmlEntity.isDmlEntity(model)) {
|
if (!DmlEntity.isDmlEntity(model)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = model.schema
|
const schema = model.schema
|
||||||
|
// @ts-ignore
|
||||||
const modelLinkConfig = (linkConfig[lowerCaseFirst(model.name)] ??= {
|
const modelLinkConfig = (linkConfig[lowerCaseFirst(model.name)] ??= {
|
||||||
toJSON: function () {
|
toJSON: function () {
|
||||||
const linkables = Object.entries(this)
|
const linkables = Object.entries(this)
|
||||||
@@ -322,7 +324,7 @@ export function buildLinkConfigFromDmlObjects<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return linkConfig as InfersLinksConfig<ServiceName, T> & Record<any, any>
|
return linkConfig as InfersLinksConfig<ServiceName, T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -353,9 +353,8 @@ export function MedusaService<
|
|||||||
class AbstractModuleService_ {
|
class AbstractModuleService_ {
|
||||||
[MedusaServiceSymbol] = true
|
[MedusaServiceSymbol] = true
|
||||||
|
|
||||||
static [MedusaServiceModelObjectsSymbol] = Object.values(
|
static [MedusaServiceModelObjectsSymbol] =
|
||||||
entities
|
entities as unknown as MedusaServiceReturnType<
|
||||||
) as unknown as MedusaServiceReturnType<
|
|
||||||
EntitiesConfig extends { __empty: any }
|
EntitiesConfig extends { __empty: any }
|
||||||
? ModelConfigurationsToConfigTemplate<TEntities>
|
? ModelConfigurationsToConfigTemplate<TEntities>
|
||||||
: EntitiesConfig
|
: EntitiesConfig
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Constructor, ModuleExports } from "@medusajs/types"
|
import { Constructor, IDmlEntity, ModuleExports } from "@medusajs/types"
|
||||||
import { MedusaServiceModelObjectsSymbol } from "./medusa-service"
|
import { MedusaServiceModelObjectsSymbol } from "./medusa-service"
|
||||||
import {
|
import {
|
||||||
buildLinkConfigFromDmlObjects,
|
buildLinkConfigFromDmlObjects,
|
||||||
defineJoinerConfig,
|
defineJoinerConfig,
|
||||||
} from "./joiner-config-builder"
|
} from "./joiner-config-builder"
|
||||||
import { InfersLinksConfig } from "./types/links-config"
|
import { InfersLinksConfig } from "./types/links-config"
|
||||||
import { DmlEntity } from "../dml"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper to build the module export and auto generate the joiner config if needed as well as
|
* Wrapper to build the module export and auto generate the joiner config if needed as well as
|
||||||
@@ -19,28 +18,32 @@ import { DmlEntity } from "../dml"
|
|||||||
export function Module<
|
export function Module<
|
||||||
const ServiceName extends string,
|
const ServiceName extends string,
|
||||||
const Service extends Constructor<any>,
|
const Service extends Constructor<any>,
|
||||||
const ModelObjects extends DmlEntity<any, any>[] = Service extends {
|
ModelObjects extends Record<string, IDmlEntity<any, any>> = Service extends {
|
||||||
$modelObjects: infer $DmlObjects
|
$modelObjects: any
|
||||||
}
|
}
|
||||||
? $DmlObjects
|
? Service["$modelObjects"]
|
||||||
: [],
|
: {},
|
||||||
Links = keyof ModelObjects extends never
|
Linkable = keyof ModelObjects extends never
|
||||||
? Record<string, any>
|
? Record<string, any>
|
||||||
: InfersLinksConfig<ServiceName, ModelObjects>
|
: InfersLinksConfig<ServiceName, ModelObjects>
|
||||||
>(
|
>(
|
||||||
serviceName: ServiceName,
|
serviceName: ServiceName,
|
||||||
{ service, loaders }: ModuleExports<Service>
|
{ service, loaders }: ModuleExports<Service>
|
||||||
): ModuleExports<Service> & {
|
): ModuleExports<Service> & {
|
||||||
links: Links
|
linkable: Linkable
|
||||||
} {
|
} {
|
||||||
service.prototype.__joinerConfig ??= defineJoinerConfig(serviceName)
|
service.prototype.__joinerConfig ??= defineJoinerConfig(serviceName)
|
||||||
|
|
||||||
const dmlObjects = service[MedusaServiceModelObjectsSymbol]
|
const dmlObjects = service[MedusaServiceModelObjectsSymbol] ?? {}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
service,
|
service,
|
||||||
loaders,
|
loaders,
|
||||||
links: (dmlObjects?.length
|
linkable: (Object.keys(dmlObjects)?.length
|
||||||
? buildLinkConfigFromDmlObjects<ServiceName, ModelObjects>(dmlObjects)
|
? buildLinkConfigFromDmlObjects<ServiceName, ModelObjects>(
|
||||||
: {}) as Links,
|
serviceName,
|
||||||
|
dmlObjects
|
||||||
|
)
|
||||||
|
: {}) as Linkable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
DMLSchema,
|
DMLSchema,
|
||||||
|
IDmlEntity,
|
||||||
IDmlEntityConfig,
|
IDmlEntityConfig,
|
||||||
InferDmlEntityNameFromConfig,
|
InferDmlEntityNameFromConfig,
|
||||||
|
Prettify,
|
||||||
SnakeCase,
|
SnakeCase,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { DmlEntity } from "../../dml"
|
|
||||||
import { PrimaryKeyModifier } from "../../dml/properties/primary-key"
|
import { PrimaryKeyModifier } from "../../dml/properties/primary-key"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,22 +55,22 @@ type InferLinkableKeyName<
|
|||||||
string}`
|
string}`
|
||||||
: never
|
: never
|
||||||
|
|
||||||
type InferSchemaLinkableKeys<T> = T extends DmlEntity<
|
type InferSchemaLinkableKeys<T> = T extends IDmlEntity<
|
||||||
infer Schema,
|
infer Schema,
|
||||||
infer Config
|
infer Config
|
||||||
>
|
>
|
||||||
? {
|
? {
|
||||||
[K in keyof Schema as Schema[K] extends PrimaryKeyModifier<any, any>
|
[K in keyof Schema as Schema[K] extends PrimaryKeyModifier<any, any>
|
||||||
? InferLinkableKeyName<K, Schema[K], Config>
|
? InferLinkableKeyName<K, Schema[K], Config>
|
||||||
: never]: InferDmlEntityNameFromConfig<Config>
|
: never]: Capitalize<InferDmlEntityNameFromConfig<Config>>
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
type InferSchemasLinkableKeys<T extends DmlEntity<any, any>[]> = {
|
type InferSchemasLinkableKeys<T extends IDmlEntity<any, any>[]> = {
|
||||||
[K in keyof T]: InferSchemaLinkableKeys<T[K]>
|
[K in keyof T]: InferSchemaLinkableKeys<T[K]>
|
||||||
}
|
}
|
||||||
|
|
||||||
type AggregateSchemasLinkableKeys<T extends DmlEntity<any, any>[]> = {
|
type AggregateSchemasLinkableKeys<T extends IDmlEntity<any, any>[]> = {
|
||||||
[K in keyof InferSchemasLinkableKeys<T>]: InferSchemasLinkableKeys<T>[K]
|
[K in keyof InferSchemasLinkableKeys<T>]: InferSchemasLinkableKeys<T>[K]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ type AggregateSchemasLinkableKeys<T extends DmlEntity<any, any>[]> = {
|
|||||||
* const linkableKeys = buildLinkableKeysFromDmlObjects([user, car]) // { user_id: 'user', car_number_plate: 'car' }
|
* const linkableKeys = buildLinkableKeysFromDmlObjects([user, car]) // { user_id: 'user', car_number_plate: 'car' }
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type InferLinkableKeys<T extends DmlEntity<any, any>[]> =
|
export type InferLinkableKeys<T extends IDmlEntity<any, any>[]> =
|
||||||
UnionToIntersection<FlattenUnion<AggregateSchemasLinkableKeys<T>>[0]>
|
UnionToIntersection<FlattenUnion<AggregateSchemasLinkableKeys<T>>[0]>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,14 +142,12 @@ type InferPrimaryKeyNameOrNever<
|
|||||||
type InferSchemaLinksConfig<
|
type InferSchemaLinksConfig<
|
||||||
ServiceName extends string,
|
ServiceName extends string,
|
||||||
T
|
T
|
||||||
> = T extends DmlEntity<infer Schema, infer Config>
|
> = T extends IDmlEntity<infer Schema, infer Config>
|
||||||
? {
|
? {
|
||||||
[K in keyof Schema as Schema[K] extends PrimaryKeyModifier<any, any>
|
[K in keyof Schema as InferPrimaryKeyNameOrNever<Schema, K>]: {
|
||||||
? InferPrimaryKeyNameOrNever<Schema, K>
|
|
||||||
: never]: {
|
|
||||||
serviceName: ServiceName
|
serviceName: ServiceName
|
||||||
field: T extends DmlEntity<any, infer Config>
|
field: T extends IDmlEntity<any, infer Config>
|
||||||
? Uncapitalize<InferDmlEntityNameFromConfig<Config>>
|
? InferDmlEntityNameFromConfig<Config>
|
||||||
: string
|
: string
|
||||||
linkable: InferLinkableKeyName<K, Schema[K], Config>
|
linkable: InferLinkableKeyName<K, Schema[K], Config>
|
||||||
primaryKey: K
|
primaryKey: K
|
||||||
@@ -200,20 +199,22 @@ type InferSchemaLinksConfig<
|
|||||||
*/
|
*/
|
||||||
export type InfersLinksConfig<
|
export type InfersLinksConfig<
|
||||||
ServiceName extends string,
|
ServiceName extends string,
|
||||||
T extends DmlEntity<any, any>[]
|
T extends Record<string, IDmlEntity<any, any>>
|
||||||
> = UnionToIntersection<{
|
> = Prettify<{
|
||||||
[K in keyof T as T[K] extends DmlEntity<any, infer Config>
|
[K in keyof T as T[K] extends IDmlEntity<any, infer Config>
|
||||||
? Uncapitalize<InferDmlEntityNameFromConfig<Config>>
|
? InferDmlEntityNameFromConfig<Config>
|
||||||
: never]: InferSchemaLinksConfig<ServiceName, T[K]> & {
|
: never]: Prettify<
|
||||||
|
InferSchemaLinksConfig<ServiceName, T[K]> & {
|
||||||
toJSON: () => {
|
toJSON: () => {
|
||||||
serviceName: ServiceName
|
serviceName: ServiceName
|
||||||
field: T[K] extends DmlEntity<any, infer Config>
|
field: T[K] extends IDmlEntity<any, infer Config>
|
||||||
? Uncapitalize<InferDmlEntityNameFromConfig<Config>>
|
? InferDmlEntityNameFromConfig<Config>
|
||||||
: string
|
: string
|
||||||
linkable: InferLastLinkable<ServiceName, T[K]>
|
linkable: InferLastLinkable<ServiceName, T[K]>
|
||||||
primaryKey: InferLastPrimaryKey<ServiceName, T[K]>
|
primaryKey: InferLastPrimaryKey<ServiceName, T[K]>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
>
|
||||||
}>
|
}>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Constructor,
|
Constructor,
|
||||||
Context,
|
Context,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
|
IDmlEntity,
|
||||||
Pluralize,
|
Pluralize,
|
||||||
RestoreReturn,
|
RestoreReturn,
|
||||||
SoftDeleteReturn,
|
SoftDeleteReturn,
|
||||||
@@ -42,7 +43,7 @@ export type ModelConfigurationsToConfigTemplate<T extends TEntityEntries> = {
|
|||||||
dto: T[Key] extends Constructor<any> ? InstanceType<T[Key]> : any
|
dto: T[Key] extends Constructor<any> ? InstanceType<T[Key]> : any
|
||||||
model: T[Key] extends { model: infer MODEL }
|
model: T[Key] extends { model: infer MODEL }
|
||||||
? MODEL
|
? MODEL
|
||||||
: T[Key] extends DmlEntity<any, any>
|
: T[Key] extends IDmlEntity<any, any>
|
||||||
? T[Key]
|
? T[Key]
|
||||||
: never
|
: never
|
||||||
/**
|
/**
|
||||||
@@ -242,20 +243,20 @@ export type AbstractModuleService<
|
|||||||
type InferModelFromConfig<T> = {
|
type InferModelFromConfig<T> = {
|
||||||
[K in keyof T as T[K] extends { model: any }
|
[K in keyof T as T[K] extends { model: any }
|
||||||
? K
|
? K
|
||||||
: K extends DmlEntity<any, any>
|
: K extends IDmlEntity<any, any>
|
||||||
? K
|
? K
|
||||||
: never]: T[K] extends {
|
: never]: T[K] extends {
|
||||||
model: infer MODEL
|
model: infer MODEL
|
||||||
}
|
}
|
||||||
? MODEL extends DmlEntity<any, any>
|
? MODEL extends IDmlEntity<any, any>
|
||||||
? MODEL
|
? MODEL
|
||||||
: never
|
: never
|
||||||
: T[K] extends DmlEntity<any, any>
|
: T[K] extends IDmlEntity<any, any>
|
||||||
? T[K]
|
? T[K]
|
||||||
: never
|
: never
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MedusaServiceReturnType<ModelsConfig extends Record<any, any>> = {
|
export type MedusaServiceReturnType<ModelsConfig extends Record<any, any>> = {
|
||||||
new (...args: any[]): AbstractModuleService<ModelsConfig>
|
new (...args: any[]): AbstractModuleService<ModelsConfig>
|
||||||
$modelObjects: InferModelFromConfig<ModelsConfig>[keyof InferModelFromConfig<ModelsConfig>][]
|
$modelObjects: InferModelFromConfig<ModelsConfig>
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import { ModuleExports } from "@medusajs/types"
|
|
||||||
import { RegionModuleService } from "./services"
|
import { RegionModuleService } from "./services"
|
||||||
import loadDefaults from "./loaders/defaults"
|
import loadDefaults from "./loaders/defaults"
|
||||||
|
import { Module, Modules } from "@medusajs/utils"
|
||||||
|
|
||||||
const moduleDefinition: ModuleExports = {
|
export default Module(Modules.REGION, {
|
||||||
service: RegionModuleService,
|
service: RegionModuleService,
|
||||||
loaders: [loadDefaults],
|
loaders: [loadDefaults],
|
||||||
}
|
})
|
||||||
export default moduleDefinition
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
|
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
|
||||||
import { ContainerRegistrationKeys, DefaultsUtils } from "@medusajs/utils"
|
import { ContainerRegistrationKeys, DefaultsUtils } from "@medusajs/utils"
|
||||||
import { RegionCountry } from "@models"
|
import { Country } from "@models"
|
||||||
|
|
||||||
export default async ({ container }: LoaderOptions): Promise<void> => {
|
export default async ({ container }: LoaderOptions): Promise<void> => {
|
||||||
// TODO: Add default logger to the container when running tests
|
// TODO: Add default logger to the container when running tests
|
||||||
const logger =
|
const logger =
|
||||||
container.resolve<Logger>(ContainerRegistrationKeys.LOGGER) ?? console
|
container.resolve<Logger>(ContainerRegistrationKeys.LOGGER) ?? console
|
||||||
const countryService_: ModulesSdkTypes.IMedusaInternalService<
|
const countryService_: ModulesSdkTypes.IMedusaInternalService<
|
||||||
typeof RegionCountry
|
typeof Country
|
||||||
> = container.resolve("countryService")
|
> = container.resolve("countryService")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { model } from "@medusajs/utils"
|
import { model } from "@medusajs/utils"
|
||||||
import Region from "./region"
|
import Region from "./region"
|
||||||
|
|
||||||
const Country = model
|
export default model
|
||||||
.define(
|
.define(
|
||||||
{ name: "Country", tableName: "region_country" },
|
{ name: "Country", tableName: "region_country" },
|
||||||
{
|
{
|
||||||
@@ -24,5 +24,3 @@ const Country = model
|
|||||||
unique: true,
|
unique: true,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
export default Country
|
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
export { default as RegionCountry } from "./country"
|
export { default as Country } from "./country"
|
||||||
export { default as Region } from "./region"
|
export { default as Region } from "./region"
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { model } from "@medusajs/utils"
|
import { model } from "@medusajs/utils"
|
||||||
import RegionCountry from "./country"
|
import RegionCountry from "./country"
|
||||||
|
|
||||||
const Region = model.define("region", {
|
export default model.define("region", {
|
||||||
id: model.id({ prefix: "reg" }).primaryKey(),
|
id: model.id({ prefix: "reg" }).primaryKey(),
|
||||||
name: model.text().searchable(),
|
name: model.text().searchable(),
|
||||||
currency_code: model.text().searchable(),
|
currency_code: model.text().searchable(),
|
||||||
@@ -9,5 +9,3 @@ const Region = model.define("region", {
|
|||||||
countries: model.hasMany(() => RegionCountry),
|
countries: model.hasMany(() => RegionCountry),
|
||||||
metadata: model.json().nullable(),
|
metadata: model.json().nullable(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Region
|
|
||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
promiseAll,
|
promiseAll,
|
||||||
removeUndefined,
|
removeUndefined,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { Region, RegionCountry as Country } from "@models"
|
import { Country, Region } from "@models"
|
||||||
import { UpdateRegionInput } from "@types"
|
import { UpdateRegionInput } from "@types"
|
||||||
import { joinerConfig } from "../joiner-config"
|
import { joinerConfig } from "../joiner-config"
|
||||||
|
|
||||||
@@ -40,9 +40,11 @@ export default class RegionModuleService
|
|||||||
extends MedusaService<{
|
extends MedusaService<{
|
||||||
Region: {
|
Region: {
|
||||||
dto: RegionDTO
|
dto: RegionDTO
|
||||||
|
model: typeof Region
|
||||||
}
|
}
|
||||||
Country: {
|
Country: {
|
||||||
dto: RegionCountryDTO
|
dto: RegionCountryDTO
|
||||||
|
model: typeof Country
|
||||||
}
|
}
|
||||||
}>({ Region, Country })
|
}>({ Region, Country })
|
||||||
implements IRegionModuleService
|
implements IRegionModuleService
|
||||||
|
|||||||
Reference in new issue
Block a user