feat(modules-sdk): define link (#7743)

This commit is contained in:
Carlos R. L. Rodrigues
2024-06-17 09:28:42 -03:00
committed by GitHub
parent 0886869148
commit bc0c65c6b3
7 changed files with 303 additions and 38 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/modules-sdk": patch
---
defineLink helper - MedusaApp loading registered links

View File

@@ -6,3 +6,4 @@ export * from "./medusa-module"
export * from "./remote-link"
export * from "./remote-query"
export * from "./types"
export * from "./utils/define-link"

View File

@@ -1,4 +1,3 @@
import type { Knex } from "knex"
import { mergeTypeDefs } from "@graphql-tools/merge"
import { makeExecutableSchema } from "@graphql-tools/schema"
import { RemoteFetchDataCallback } from "@medusajs/orchestration"
@@ -26,12 +25,13 @@ import {
promiseAll,
} from "@medusajs/utils"
import { asValue } from "awilix"
import type { Knex } from "knex"
import {
MODULE_PACKAGE_NAMES,
ModuleRegistrationName,
Modules,
} from "./definitions"
import { MedusaModule } from "./medusa-module"
import { MedusaModule, RegisterModuleJoinerConfig } from "./medusa-module"
import { RemoteLink } from "./remote-link"
import { RemoteQuery } from "./remote-query"
import { MODULE_RESOURCE_TYPE, MODULE_SCOPE } from "./types"
@@ -240,7 +240,7 @@ export type MedusaAppOptions = {
modulesConfigPath?: string
modulesConfigFileName?: string
modulesConfig?: MedusaModuleConfig
linkModules?: ModuleJoinerConfig | ModuleJoinerConfig[]
linkModules?: RegisterModuleJoinerConfig | RegisterModuleJoinerConfig[]
remoteFetchData?: RemoteFetchDataCallback
injectedDependencies?: any
onApplicationStartCb?: () => void
@@ -260,7 +260,6 @@ async function MedusaApp_({
linkModules,
remoteFetchData,
injectedDependencies = {},
onApplicationStartCb,
migrationOnly = false,
loaderOnly = false,
workerMode = "server",
@@ -363,6 +362,20 @@ async function MedusaApp_({
allowUnregistered: true,
})
linkModules ??= []
if (!Array.isArray(linkModules)) {
linkModules = [linkModules]
}
linkModules.push(...MedusaModule.getCustomLinks())
const allLoadedJoinerConfigs = MedusaModule.getAllJoinerConfigs()
for (let linkIdx = 0; linkIdx < linkModules.length; linkIdx++) {
const customLink: any = linkModules[linkIdx]
if (typeof customLink === "function") {
linkModules[linkIdx] = customLink(allLoadedJoinerConfigs)
}
}
const {
remoteLink,
runMigrations: linkModuleMigration,

View File

@@ -80,10 +80,15 @@ export type LinkModuleBootstrapOptions = {
injectedDependencies?: Record<string, any>
}
export type RegisterModuleJoinerConfig =
| ModuleJoinerConfig
| ((modules: ModuleJoinerConfig[]) => ModuleJoinerConfig)
export class MedusaModule {
private static instances_: Map<string, { [key: string]: IModuleService }> =
new Map()
private static modules_: Map<string, ModuleAlias[]> = new Map()
private static customLinks_: RegisterModuleJoinerConfig[] = []
private static loading_: Map<string, Promise<any>> = new Map()
private static joinerConfig_: Map<string, ModuleJoinerConfig> = new Map()
private static moduleResolutions_: Map<string, ModuleResolution> = new Map()
@@ -203,6 +208,14 @@ export class MedusaModule {
return config
}
public static setCustomLink(config: RegisterModuleJoinerConfig): void {
MedusaModule.customLinks_.push(config)
}
public static getCustomLinks(): RegisterModuleJoinerConfig[] {
return MedusaModule.customLinks_
}
public static getModuleInstance(
moduleKey: string,
alias?: string

View File

@@ -0,0 +1,232 @@
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)
}

View File

@@ -1,2 +1,3 @@
export * from "./clean-graphql-schema"
export * from "./define-link"
export * from "./graphql-schema-to-fields"

View File

@@ -129,28 +129,39 @@ export type ModulesResponse = {
resolution: string | false
}[]
type ExtraFieldType =
| "date"
| "time"
| "datetime"
| "bigint"
| "blob"
| "uint8array"
| "array"
| "enumArray"
| "enum"
| "json"
| "integer"
| "smallint"
| "tinyint"
| "mediumint"
| "float"
| "double"
| "boolean"
| "decimal"
| "string"
| "uuid"
| "text"
export type LinkModulesExtraFields = Record<
string,
{
type:
| "date"
| "time"
| "datetime"
| "bigint"
| "blob"
| "uint8array"
| "array"
| "enumArray"
| "enum"
| "json"
| "integer"
| "smallint"
| "tinyint"
| "mediumint"
| "float"
| "double"
| "boolean"
| "decimal"
| "string"
| "uuid"
| "text"
defaultValue?: string
nullable?: boolean
/**
* Mikro-orm options for the column
*/
options?: Record<string, unknown>
}
>
export type ModuleJoinerConfig = Omit<
JoinerServiceConfig,
@@ -202,18 +213,7 @@ export type ModuleJoinerConfig = Omit<
* Prefix for the id column. If not provided it is "link"
*/
idPrefix?: string
extraFields?: Record<
string,
{
type: ExtraFieldType
defaultValue?: string
nullable?: boolean
/**
* Mikro-orm options for the column
*/
options?: Record<string, unknown>
}
>
extraFields?: LinkModulesExtraFields
}
}