feat: Improve startup time by parallelizing module and link loading (#12731)

This commit is contained in:
Stevche Radevski authored and GitHub committed 2025-06-13 13:11:19 +02:00
1 parent 3b1dc0ced0
commit bff5c00777
3 files changed
+199 -188

No files matched your search

@@ -116,7 +116,7 @@ export const declineOrderTransferRequestWorkflow = createWorkflow(
): WorkflowData<void> { ): WorkflowData<void> {
const orderQuery = useQueryGraphStep({ const orderQuery = useQueryGraphStep({
entity: "order", entity: "order",
fields: ["id", "version", "declineed_at"], fields: ["id", "version", "declined_at"],
filters: { id: input.order_id }, filters: { id: input.order_id },
options: { throwIfKeyNotFound: true }, options: { throwIfKeyNotFound: true },
}).config({ name: "order-query" }) }).config({ name: "order-query" })
+84 -79
View File
@@ -395,104 +395,109 @@ class MedusaModule {
const services: { [Key: string]: any }[] = [] const services: { [Key: string]: any }[] = []
for (const moduleOptions of modulesOptions) { await promiseAll(
const { modulesOptions.map(async (moduleOptions) => {
moduleKey, const {
defaultPath, moduleKey,
declaration, defaultPath,
moduleExports, declaration,
sharedContainer, moduleExports,
moduleDefinition, sharedContainer,
injectedDependencies, moduleDefinition,
} = moduleOptions injectedDependencies,
} = moduleOptions
const hashKey = simpleHash( const hashKey = simpleHash(
stringifyCircular({ moduleKey, defaultPath, declaration }) stringifyCircular({ moduleKey, defaultPath, declaration })
) )
let finishLoading: any let finishLoading: any
let errorLoading: any let errorLoading: any
const loadingPromise = new Promise((resolve, reject) => { const loadingPromise = new Promise((resolve, reject) => {
finishLoading = resolve finishLoading = resolve
errorLoading = reject errorLoading = reject
}) })
if (!loaderOnly && MedusaModule.instances_.has(hashKey)) { if (!loaderOnly && MedusaModule.instances_.has(hashKey)) {
services.push(MedusaModule.instances_.get(hashKey)!) services.push(MedusaModule.instances_.get(hashKey)!)
continue return
} }
if (!loaderOnly && MedusaModule.loading_.has(hashKey)) { if (!loaderOnly && MedusaModule.loading_.has(hashKey)) {
services.push(await MedusaModule.loading_.get(hashKey)) services.push(await MedusaModule.loading_.get(hashKey))
continue return
} }
if (!loaderOnly) { if (!loaderOnly) {
MedusaModule.loading_.set(hashKey, loadingPromise) MedusaModule.loading_.set(hashKey, loadingPromise)
} }
let modDeclaration = let modDeclaration =
declaration ?? declaration ??
({} as InternalModuleDeclaration | ExternalModuleDeclaration) ({} as InternalModuleDeclaration | ExternalModuleDeclaration)
if (declaration?.scope !== MODULE_SCOPE.EXTERNAL) { if (declaration?.scope !== MODULE_SCOPE.EXTERNAL) {
modDeclaration = { modDeclaration = {
scope: declaration?.scope || MODULE_SCOPE.INTERNAL, scope: declaration?.scope || MODULE_SCOPE.INTERNAL,
resolve: defaultPath, resolve: defaultPath,
options: declaration?.options ?? declaration, options: declaration?.options ?? declaration,
dependencies: dependencies:
(declaration as InternalModuleDeclaration)?.dependencies ?? [], (declaration as InternalModuleDeclaration)?.dependencies ?? [],
alias: declaration?.alias, alias: declaration?.alias,
main: declaration?.main, main: declaration?.main,
worker_mode: workerMode, worker_mode: workerMode,
} as InternalModuleDeclaration } as InternalModuleDeclaration
} }
const container = sharedContainer ?? createMedusaContainer() const container = sharedContainer ?? createMedusaContainer()
if (injectedDependencies) { if (injectedDependencies) {
for (const service in injectedDependencies) { for (const service in injectedDependencies) {
container.register(service, asValue(injectedDependencies[service]))
if (!container.hasRegistration(service)) {
container.register(service, asValue(injectedDependencies[service])) container.register(service, asValue(injectedDependencies[service]))
if (!container.hasRegistration(service)) {
container.register(
service,
asValue(injectedDependencies[service])
)
}
} }
} }
}
const moduleResolutions = registerMedusaModule( const moduleResolutions = registerMedusaModule(
moduleKey, moduleKey,
modDeclaration!, modDeclaration!,
moduleExports, moduleExports,
moduleDefinition moduleDefinition
) )
const logger_ = const logger_ =
container.resolve(ContainerRegistrationKeys.LOGGER, { container.resolve(ContainerRegistrationKeys.LOGGER, {
allowUnregistered: true, allowUnregistered: true,
}) ?? logger }) ?? logger
try { try {
await moduleLoader({ await moduleLoader({
container, container,
moduleResolutions,
logger: logger_,
migrationOnly,
loaderOnly,
})
} catch (err) {
errorLoading(err)
throw err
}
loadedModules.push({
hashKey,
modDeclaration,
moduleResolutions, moduleResolutions,
logger: logger_, container,
migrationOnly, finishLoading,
loaderOnly,
}) })
} catch (err) {
errorLoading(err)
throw err
}
loadedModules.push({
hashKey,
modDeclaration,
moduleResolutions,
container,
finishLoading,
}) })
} )
if (loaderOnly) { if (loaderOnly) {
loadedModules.forEach(({ finishLoading }) => finishLoading({})) loadedModules.forEach(({ finishLoading }) => finishLoading({}))
@@ -15,6 +15,7 @@ import {
composeTableName, composeTableName,
ContainerRegistrationKeys, ContainerRegistrationKeys,
Modules, Modules,
promiseAll,
simpleHash, simpleHash,
toPascalCase, toPascalCase,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
@@ -42,130 +43,135 @@ export const initialize = async (
pluginLinksDefinitions ?? [] pluginLinksDefinitions ?? []
) )
for (const linkDefinition of allLinksToLoad) { await promiseAll(
const definition: ModuleJoinerConfig = JSON.parse( allLinksToLoad.map(async (linkDefinition) => {
JSON.stringify(linkDefinition) 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 [primary, foreign] = definition.relationships ?? []
const extraDataFields = definition.extraDataFields
const definedDbFields = Object.keys(
definition.databaseConfig?.extraFields || {}
)
const difference = arrayDifference(extraDataFields, definedDbFields)
if (difference.length) { if (
definition.relationships?.length !== 2 &&
!definition.isReadOnlyLink
) {
throw new Error( throw new Error(
`extraDataFields (fieldNames: ${difference.join( `Link module ${definition.serviceName} can only link 2 modules.`
","
)}) need to be configured under databaseConfig (serviceName: ${
definition.serviceName
}).`
) )
} else if (
foreign?.foreignKey?.split(",").length > 1 &&
!definition.isReadOnlyLink
) {
throw new Error(`Foreign key cannot be a composed key.`)
} }
}
const serviceKey = !definition.isReadOnlyLink if (Array.isArray(definition.extraDataFields)) {
? definition.serviceName ?? const extraDataFields = definition.extraDataFields
composeLinkName( const definedDbFields = Object.keys(
primary.serviceName, definition.databaseConfig?.extraFields || {}
primary.foreignKey,
foreign.serviceName,
foreign.foreignKey
) )
: simpleHash(JSON.stringify(definition.extends)) const difference = arrayDifference(extraDataFields, definedDbFields)
if (modulesLoadedKeys.includes(serviceKey)) { if (difference.length) {
continue throw new Error(
} else if (serviceKey in allLinks) { `extraDataFields (fieldNames: ${difference.join(
throw new Error(`Link module ${serviceKey} already defined.`) ","
} )}) need to be configured under databaseConfig (serviceName: ${
definition.serviceName
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 const serviceKey = !definition.isReadOnlyLink
if (extended.length === 0) { ? definition.serviceName ??
continue composeLinkName(
primary.serviceName,
primary.foreignKey,
foreign.serviceName,
foreign.foreignKey
)
: simpleHash(JSON.stringify(definition.extends))
if (modulesLoadedKeys.includes(serviceKey)) {
return
} else if (serviceKey in allLinks) {
throw new Error(`Link module ${serviceKey} already defined.`)
} }
} else if (
!modulesLoadedKeys.includes(primary.serviceName) ||
!modulesLoadedKeys.includes(foreign.serviceName)
) {
continue
}
const logger = if (definition.isReadOnlyLink) {
injectedDependencies?.[ContainerRegistrationKeys.LOGGER] ?? console const extended: any[] = []
for (const extension of definition.extends ?? []) {
if (
modulesLoadedKeys.includes(extension.serviceName) &&
modulesLoadedKeys.includes(extension.relationship.serviceName)
) {
extended.push(extension)
}
}
definition.schema = generateGraphQLSchema(definition, primary, foreign, { definition.extends = extended
logger, if (extended.length === 0) {
return
}
} else if (
!modulesLoadedKeys.includes(primary.serviceName) ||
!modulesLoadedKeys.includes(foreign.serviceName)
) {
return
}
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]
}) })
)
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 return allLinks
} }