What: * removes resouces type "shared" or "isolated" from internal modules. * modules can have an isolated database connection by providing a database config as part of their options on `medusa-config` CLOSES: FRMW-2593
231 lines
6.4 KiB
TypeScript
231 lines
6.4 KiB
TypeScript
import { MedusaModule, MODULE_SCOPE } from "@medusajs/framework/modules-sdk"
|
|
import {
|
|
ExternalModuleDeclaration,
|
|
ILinkModule,
|
|
InternalModuleDeclaration,
|
|
LinkModuleDefinition,
|
|
ModuleExports,
|
|
ModuleJoinerConfig,
|
|
ModuleServiceInitializeCustomDataLayerOptions,
|
|
ModuleServiceInitializeOptions,
|
|
} from "@medusajs/framework/types"
|
|
import {
|
|
arrayDifference,
|
|
composeLinkName,
|
|
composeTableName,
|
|
ContainerRegistrationKeys,
|
|
Modules,
|
|
simpleHash,
|
|
toPascalCase,
|
|
} from "@medusajs/framework/utils"
|
|
import * as linkDefinitions from "../definitions"
|
|
import { MigrationsExecutionPlanner } from "../migration"
|
|
import { InitializeModuleInjectableDependencies } from "../types"
|
|
import { generateGraphQLSchema } from "../utils"
|
|
import { getLinkModuleDefinition } from "./module-definition"
|
|
|
|
export const initialize = async (
|
|
options?:
|
|
| ModuleServiceInitializeOptions
|
|
| ModuleServiceInitializeCustomDataLayerOptions
|
|
| ExternalModuleDeclaration
|
|
| InternalModuleDeclaration,
|
|
pluginLinksDefinitions?: ModuleJoinerConfig[],
|
|
injectedDependencies?: InitializeModuleInjectableDependencies
|
|
): Promise<{ [link: string]: ILinkModule }> => {
|
|
const allLinks = {}
|
|
const modulesLoadedKeys = MedusaModule.getLoadedModules().map(
|
|
(mod) => Object.keys(mod)[0]
|
|
)
|
|
|
|
const allLinksToLoad = Object.values(linkDefinitions).concat(
|
|
pluginLinksDefinitions ?? []
|
|
)
|
|
|
|
for (const linkDefinition of allLinksToLoad) {
|
|
const definition: ModuleJoinerConfig = JSON.parse(
|
|
JSON.stringify(linkDefinition)
|
|
)
|
|
|
|
const [primary, foreign] = definition.relationships ?? []
|
|
|
|
if (definition.relationships?.length !== 2 && !definition.isReadOnlyLink) {
|
|
throw new Error(
|
|
`Link module ${definition.serviceName} can only link 2 modules.`
|
|
)
|
|
} else if (
|
|
foreign?.foreignKey?.split(",").length > 1 &&
|
|
!definition.isReadOnlyLink
|
|
) {
|
|
throw new Error(`Foreign key cannot be a composed key.`)
|
|
}
|
|
|
|
if (Array.isArray(definition.extraDataFields)) {
|
|
const extraDataFields = definition.extraDataFields
|
|
const definedDbFields = Object.keys(
|
|
definition.databaseConfig?.extraFields || {}
|
|
)
|
|
const difference = arrayDifference(extraDataFields, definedDbFields)
|
|
|
|
if (difference.length) {
|
|
throw new Error(
|
|
`extraDataFields (fieldNames: ${difference.join(
|
|
","
|
|
)}) need to be configured under databaseConfig (serviceName: ${
|
|
definition.serviceName
|
|
}).`
|
|
)
|
|
}
|
|
}
|
|
|
|
const serviceKey = !definition.isReadOnlyLink
|
|
? definition.serviceName ??
|
|
composeLinkName(
|
|
primary.serviceName,
|
|
primary.foreignKey,
|
|
foreign.serviceName,
|
|
foreign.foreignKey
|
|
)
|
|
: simpleHash(JSON.stringify(definition.extends))
|
|
|
|
if (modulesLoadedKeys.includes(serviceKey)) {
|
|
continue
|
|
} else if (serviceKey in allLinks) {
|
|
throw new Error(`Link module ${serviceKey} already defined.`)
|
|
}
|
|
|
|
if (definition.isReadOnlyLink) {
|
|
const extended: any[] = []
|
|
for (const extension of definition.extends ?? []) {
|
|
if (
|
|
modulesLoadedKeys.includes(extension.serviceName) &&
|
|
modulesLoadedKeys.includes(extension.relationship.serviceName)
|
|
) {
|
|
extended.push(extension)
|
|
}
|
|
}
|
|
|
|
definition.extends = extended
|
|
if (extended.length === 0) {
|
|
continue
|
|
}
|
|
} else if (
|
|
!modulesLoadedKeys.includes(primary.serviceName) ||
|
|
!modulesLoadedKeys.includes(foreign.serviceName)
|
|
) {
|
|
continue
|
|
}
|
|
|
|
const logger =
|
|
injectedDependencies?.[ContainerRegistrationKeys.LOGGER] ?? console
|
|
|
|
definition.schema = generateGraphQLSchema(definition, primary, foreign, {
|
|
logger,
|
|
})
|
|
|
|
if (!Array.isArray(definition.alias)) {
|
|
definition.alias = definition.alias ? [definition.alias] : []
|
|
}
|
|
|
|
for (const alias of definition.alias) {
|
|
alias.args ??= {}
|
|
|
|
alias.entity = toPascalCase(
|
|
"Link_" +
|
|
(definition.databaseConfig?.tableName ??
|
|
composeTableName(
|
|
primary.serviceName,
|
|
primary.foreignKey,
|
|
foreign.serviceName,
|
|
foreign.foreignKey
|
|
))
|
|
)
|
|
}
|
|
|
|
const moduleDefinition = getLinkModuleDefinition(
|
|
definition,
|
|
primary,
|
|
foreign
|
|
) as ModuleExports
|
|
|
|
const linkModuleDefinition: LinkModuleDefinition = {
|
|
key: serviceKey,
|
|
label: serviceKey,
|
|
dependencies: [Modules.EVENT_BUS],
|
|
defaultModuleDeclaration: {
|
|
scope: MODULE_SCOPE.INTERNAL,
|
|
},
|
|
}
|
|
|
|
const loaded = await MedusaModule.bootstrapLink({
|
|
definition: linkModuleDefinition,
|
|
declaration: options as InternalModuleDeclaration,
|
|
moduleExports: moduleDefinition,
|
|
injectedDependencies,
|
|
})
|
|
|
|
allLinks[serviceKey as string] = Object.values(loaded)[0]
|
|
}
|
|
|
|
return allLinks
|
|
}
|
|
|
|
/**
|
|
* Prepare an execution plan and run the migrations accordingly.
|
|
* It includes creating, updating, deleting the tables according to the execution plan.
|
|
* If any unsafe sql is identified then we will notify the user to act manually.
|
|
*
|
|
* @param options
|
|
* @param pluginLinksDefinition
|
|
*/
|
|
export function getMigrationPlanner(
|
|
options: ModuleServiceInitializeOptions,
|
|
pluginLinksDefinition?: ModuleJoinerConfig[]
|
|
) {
|
|
const modulesLoadedKeys = MedusaModule.getLoadedModules().map(
|
|
(mod) => Object.keys(mod)[0]
|
|
)
|
|
|
|
const allLinksToLoad = Object.values(linkDefinitions).concat(
|
|
pluginLinksDefinition ?? []
|
|
)
|
|
|
|
const allLinks = new Set<string>()
|
|
for (const definition of allLinksToLoad) {
|
|
if (definition.isReadOnlyLink) {
|
|
continue
|
|
}
|
|
|
|
if (definition.relationships?.length !== 2) {
|
|
throw new Error(
|
|
`Link module ${definition.serviceName} must have 2 relationships.`
|
|
)
|
|
}
|
|
|
|
const [primary, foreign] = definition.relationships ?? []
|
|
const serviceKey =
|
|
definition.serviceName ??
|
|
composeLinkName(
|
|
primary.serviceName,
|
|
primary.foreignKey,
|
|
foreign.serviceName,
|
|
foreign.foreignKey
|
|
)
|
|
|
|
if (allLinks.has(serviceKey)) {
|
|
throw new Error(`Link module ${serviceKey} already exists.`)
|
|
}
|
|
|
|
allLinks.add(serviceKey)
|
|
|
|
if (
|
|
!modulesLoadedKeys.includes(primary.serviceName) ||
|
|
!modulesLoadedKeys.includes(foreign.serviceName)
|
|
) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
return new MigrationsExecutionPlanner(allLinksToLoad, options)
|
|
}
|