feat(modules-sdk): define link (#7743)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/modules-sdk": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
defineLink helper - MedusaApp loading registered links
|
||||||
@@ -6,3 +6,4 @@ 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,4 +1,3 @@
|
|||||||
import type { Knex } from "knex"
|
|
||||||
import { mergeTypeDefs } from "@graphql-tools/merge"
|
import { mergeTypeDefs } from "@graphql-tools/merge"
|
||||||
import { makeExecutableSchema } from "@graphql-tools/schema"
|
import { makeExecutableSchema } from "@graphql-tools/schema"
|
||||||
import { RemoteFetchDataCallback } from "@medusajs/orchestration"
|
import { RemoteFetchDataCallback } from "@medusajs/orchestration"
|
||||||
@@ -26,12 +25,13 @@ import {
|
|||||||
promiseAll,
|
promiseAll,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { asValue } from "awilix"
|
import { asValue } from "awilix"
|
||||||
|
import type { Knex } from "knex"
|
||||||
import {
|
import {
|
||||||
MODULE_PACKAGE_NAMES,
|
MODULE_PACKAGE_NAMES,
|
||||||
ModuleRegistrationName,
|
ModuleRegistrationName,
|
||||||
Modules,
|
Modules,
|
||||||
} from "./definitions"
|
} from "./definitions"
|
||||||
import { MedusaModule } from "./medusa-module"
|
import { MedusaModule, RegisterModuleJoinerConfig } from "./medusa-module"
|
||||||
import { RemoteLink } from "./remote-link"
|
import { RemoteLink } from "./remote-link"
|
||||||
import { RemoteQuery } from "./remote-query"
|
import { RemoteQuery } from "./remote-query"
|
||||||
import { MODULE_RESOURCE_TYPE, MODULE_SCOPE } from "./types"
|
import { MODULE_RESOURCE_TYPE, MODULE_SCOPE } from "./types"
|
||||||
@@ -240,7 +240,7 @@ export type MedusaAppOptions = {
|
|||||||
modulesConfigPath?: string
|
modulesConfigPath?: string
|
||||||
modulesConfigFileName?: string
|
modulesConfigFileName?: string
|
||||||
modulesConfig?: MedusaModuleConfig
|
modulesConfig?: MedusaModuleConfig
|
||||||
linkModules?: ModuleJoinerConfig | ModuleJoinerConfig[]
|
linkModules?: RegisterModuleJoinerConfig | RegisterModuleJoinerConfig[]
|
||||||
remoteFetchData?: RemoteFetchDataCallback
|
remoteFetchData?: RemoteFetchDataCallback
|
||||||
injectedDependencies?: any
|
injectedDependencies?: any
|
||||||
onApplicationStartCb?: () => void
|
onApplicationStartCb?: () => void
|
||||||
@@ -260,7 +260,6 @@ async function MedusaApp_({
|
|||||||
linkModules,
|
linkModules,
|
||||||
remoteFetchData,
|
remoteFetchData,
|
||||||
injectedDependencies = {},
|
injectedDependencies = {},
|
||||||
onApplicationStartCb,
|
|
||||||
migrationOnly = false,
|
migrationOnly = false,
|
||||||
loaderOnly = false,
|
loaderOnly = false,
|
||||||
workerMode = "server",
|
workerMode = "server",
|
||||||
@@ -363,6 +362,20 @@ async function MedusaApp_({
|
|||||||
allowUnregistered: true,
|
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 {
|
const {
|
||||||
remoteLink,
|
remoteLink,
|
||||||
runMigrations: linkModuleMigration,
|
runMigrations: linkModuleMigration,
|
||||||
|
|||||||
@@ -80,10 +80,15 @@ export type LinkModuleBootstrapOptions = {
|
|||||||
injectedDependencies?: Record<string, any>
|
injectedDependencies?: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RegisterModuleJoinerConfig =
|
||||||
|
| ModuleJoinerConfig
|
||||||
|
| ((modules: ModuleJoinerConfig[]) => ModuleJoinerConfig)
|
||||||
|
|
||||||
export class MedusaModule {
|
export class MedusaModule {
|
||||||
private static instances_: Map<string, { [key: string]: IModuleService }> =
|
private static instances_: Map<string, { [key: string]: IModuleService }> =
|
||||||
new Map()
|
new Map()
|
||||||
private static modules_: Map<string, ModuleAlias[]> = 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 loading_: Map<string, Promise<any>> = new Map()
|
||||||
private static joinerConfig_: Map<string, ModuleJoinerConfig> = new Map()
|
private static joinerConfig_: Map<string, ModuleJoinerConfig> = new Map()
|
||||||
private static moduleResolutions_: Map<string, ModuleResolution> = new Map()
|
private static moduleResolutions_: Map<string, ModuleResolution> = new Map()
|
||||||
@@ -203,6 +208,14 @@ export class MedusaModule {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static setCustomLink(config: RegisterModuleJoinerConfig): void {
|
||||||
|
MedusaModule.customLinks_.push(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getCustomLinks(): RegisterModuleJoinerConfig[] {
|
||||||
|
return MedusaModule.customLinks_
|
||||||
|
}
|
||||||
|
|
||||||
public static getModuleInstance(
|
public static getModuleInstance(
|
||||||
moduleKey: string,
|
moduleKey: string,
|
||||||
alias?: string
|
alias?: string
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
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"
|
||||||
|
|||||||
@@ -129,28 +129,39 @@ export type ModulesResponse = {
|
|||||||
resolution: string | false
|
resolution: string | false
|
||||||
}[]
|
}[]
|
||||||
|
|
||||||
type ExtraFieldType =
|
export type LinkModulesExtraFields = Record<
|
||||||
| "date"
|
string,
|
||||||
| "time"
|
{
|
||||||
| "datetime"
|
type:
|
||||||
| "bigint"
|
| "date"
|
||||||
| "blob"
|
| "time"
|
||||||
| "uint8array"
|
| "datetime"
|
||||||
| "array"
|
| "bigint"
|
||||||
| "enumArray"
|
| "blob"
|
||||||
| "enum"
|
| "uint8array"
|
||||||
| "json"
|
| "array"
|
||||||
| "integer"
|
| "enumArray"
|
||||||
| "smallint"
|
| "enum"
|
||||||
| "tinyint"
|
| "json"
|
||||||
| "mediumint"
|
| "integer"
|
||||||
| "float"
|
| "smallint"
|
||||||
| "double"
|
| "tinyint"
|
||||||
| "boolean"
|
| "mediumint"
|
||||||
| "decimal"
|
| "float"
|
||||||
| "string"
|
| "double"
|
||||||
| "uuid"
|
| "boolean"
|
||||||
| "text"
|
| "decimal"
|
||||||
|
| "string"
|
||||||
|
| "uuid"
|
||||||
|
| "text"
|
||||||
|
defaultValue?: string
|
||||||
|
nullable?: boolean
|
||||||
|
/**
|
||||||
|
* Mikro-orm options for the column
|
||||||
|
*/
|
||||||
|
options?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
export type ModuleJoinerConfig = Omit<
|
export type ModuleJoinerConfig = Omit<
|
||||||
JoinerServiceConfig,
|
JoinerServiceConfig,
|
||||||
@@ -202,18 +213,7 @@ export type ModuleJoinerConfig = Omit<
|
|||||||
* Prefix for the id column. If not provided it is "link"
|
* Prefix for the id column. If not provided it is "link"
|
||||||
*/
|
*/
|
||||||
idPrefix?: string
|
idPrefix?: string
|
||||||
extraFields?: Record<
|
extraFields?: LinkModulesExtraFields
|
||||||
string,
|
|
||||||
{
|
|
||||||
type: ExtraFieldType
|
|
||||||
defaultValue?: string
|
|
||||||
nullable?: boolean
|
|
||||||
/**
|
|
||||||
* Mikro-orm options for the column
|
|
||||||
*/
|
|
||||||
options?: Record<string, unknown>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user