Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(modules-sdk): define link #7022

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/long-waves-itch.md
@@ -0,0 +1,5 @@
---
"@medusajs/modules-sdk": patch
---

defineLink helper - MedusaApp loading registered links
1 change: 1 addition & 0 deletions packages/modules-sdk/src/index.ts
Expand Up @@ -5,4 +5,5 @@ export * from "./medusa-app"
export * from "./medusa-module"
export * from "./remote-link"
export * from "./remote-query"
export * from "./utils/define-link"
export * from "./utils/initialize-factory"
30 changes: 23 additions & 7 deletions packages/modules-sdk/src/medusa-app.ts
Expand Up @@ -30,7 +30,7 @@ import {
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 { cleanGraphQLSchema } from "./utils"
Expand Down Expand Up @@ -216,7 +216,7 @@ export type MedusaAppOptions = {
modulesConfigPath?: string
modulesConfigFileName?: string
modulesConfig?: MedusaModuleConfig
linkModules?: ModuleJoinerConfig | ModuleJoinerConfig[]
linkModules?: RegisterModuleJoinerConfig | RegisterModuleJoinerConfig[]
remoteFetchData?: RemoteFetchDataCallback
injectedDependencies?: any
onApplicationStartCb?: () => void
Expand All @@ -236,7 +236,6 @@ async function MedusaApp_({
linkModules,
remoteFetchData,
injectedDependencies = {},
onApplicationStartCb,
migrationOnly = false,
loaderOnly = false,
workerMode = "server",
Expand Down Expand Up @@ -336,6 +335,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 } =
await initializeLinks({
config: linkModuleOptions,
Expand Down Expand Up @@ -387,10 +400,13 @@ async function MedusaApp_({
}

linkModuleMigration &&
(await linkModuleMigration({
options: linkModuleOpt,
injectedDependencies,
}))
(await linkModuleMigration(
{
options: linkModuleOpt,
injectedDependencies,
},
linkModules
))
}

return {
Expand Down
13 changes: 13 additions & 0 deletions packages/modules-sdk/src/medusa-module.ts
Expand Up @@ -82,10 +82,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()
Expand Down Expand Up @@ -205,6 +210,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
Expand Down
232 changes: 232 additions & 0 deletions packages/modules-sdk/src/utils/define-link.ts
@@ -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 change: 1 addition & 0 deletions packages/modules-sdk/src/utils/index.ts
@@ -1,3 +1,4 @@
export * from "./clean-graphql-schema"
export * from "./define-link"
export * from "./graphql-schema-to-fields"
export * from "./initialize-factory"