chore(framework): medusa app loaders (#8393)

RESOLVE FRMW-2636

**What**
cleanup and move medusa app loader for now
This commit is contained in:
Adrien de Peretti
2024-08-02 12:38:36 +02:00
committed by GitHub
parent c4ff8c27f0
commit 9950a7efe2
8 changed files with 308 additions and 358 deletions

View File

@@ -7,15 +7,13 @@ export async function initDb({ env = {} }: { env?: Record<any, any> }) {
}
const {
configManager,
pgConnectionLoader,
logger,
container,
featureFlagsLoader,
MedusaAppLoader,
} = await import("@medusajs/framework")
const configModule = configManager.config
const pgConnection = pgConnectionLoader()
await featureFlagsLoader()
@@ -24,15 +22,9 @@ export async function initDb({ env = {} }: { env?: Record<any, any> }) {
})
try {
const {
runMedusaAppMigrations,
getLinksExecutionPlanner,
} = require("@medusajs/medusa/dist/loaders/medusa-app")
await runMedusaAppMigrations({ configModule, container })
const planner = await getLinksExecutionPlanner({
configModule,
container,
})
const medusaAppLoader = new MedusaAppLoader()
await medusaAppLoader.runModulesMigrations()
const planner = await medusaAppLoader.getLinksExecutionPlanner()
const actionPlan = await planner.createPlan()
await planner.executePlan(actionPlan)

View File

@@ -1,8 +1,5 @@
import { ContainerLike, MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
createMedusaContainer,
} from "@medusajs/utils"
import { createMedusaContainer } from "@medusajs/utils"
import { createDatabase, dropDatabase } from "pg-god"
import { getDatabaseURL } from "./database"
import { startApp } from "./medusa-test-runner-utils/bootstrap-app"
@@ -200,14 +197,12 @@ export function medusaIntegrationTestRunner({
const copiedContainer = createMedusaContainer({}, container)
try {
const medusaAppLoaderRunner =
require("@medusajs/medusa/dist/loaders/medusa-app").runModulesLoader
await medusaAppLoaderRunner({
const { MedusaAppLoader } = await import("@medusajs/framework")
const medusaAppLoader = new MedusaAppLoader({
container: copiedContainer,
configModule: container.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
),
})
await medusaAppLoader.runModulesLoader()
} catch (error) {
console.error("Error runner modules loaders", error?.message)
throw error

View File

@@ -8,3 +8,4 @@ export * from "./links"
export * from "./jobs"
export * from "./feature-flags"
export * from "./workflows"
export * from "./medusa-app-loader"

View File

@@ -0,0 +1,285 @@
import {
MedusaApp,
MedusaAppGetLinksExecutionPlanner,
MedusaAppMigrateDown,
MedusaAppMigrateGenerate,
MedusaAppMigrateUp,
MedusaAppOutput,
ModulesDefinition,
RegisterModuleJoinerConfig,
} from "@medusajs/modules-sdk"
import {
CommonTypes,
ConfigModule,
ILinkMigrationsPlanner,
InternalModuleDeclaration,
LoadedModule,
ModuleDefinition,
ModuleServiceInitializeOptions,
} from "@medusajs/types"
import {
ContainerRegistrationKeys,
isBoolean,
isObject,
isPresent,
upperCaseFirst,
} from "@medusajs/utils"
import { pgConnectionLoader } from "./database"
import { asValue } from "awilix"
import {
container,
container as mainContainer,
MedusaContainer,
} from "./container"
import { configManager } from "./config"
export class MedusaAppLoader {
/**
* Container from where to resolve resources
* @private
*/
readonly #container: MedusaContainer
/**
* Extra links modules config which should be added manually to the links to be loaded
* @private
*/
readonly #customLinksModules:
| RegisterModuleJoinerConfig
| RegisterModuleJoinerConfig[]
// TODO: Adjust all loaders to accept an optional container such that in test env it is possible if needed to provide a specific container otherwise use the main container
// Maybe also adjust the different places to resolve the config from the container instead of the configManager for the same reason
// To be discussed
constructor({
container,
customLinksModules,
}: {
container?: MedusaContainer
customLinksModules?:
| RegisterModuleJoinerConfig
| RegisterModuleJoinerConfig[]
} = {}) {
this.#container = container ?? mainContainer
this.#customLinksModules = customLinksModules ?? []
}
protected mergeDefaultModules(
modulesConfig: CommonTypes.ConfigModule["modules"]
) {
const defaultModules = Object.values(ModulesDefinition).filter(
(definition: ModuleDefinition) => {
return !!definition.defaultPackage
}
)
const configModules = { ...modulesConfig } ?? {}
for (const defaultModule of defaultModules as ModuleDefinition[]) {
configModules[defaultModule.key] ??=
defaultModule.defaultModuleDeclaration
}
for (const [key, value] of Object.entries(
configModules as Record<string, InternalModuleDeclaration>
)) {
const def = {} as ModuleDefinition
def.key ??= key
def.registrationName ??= ModulesDefinition[key]?.registrationName ?? key
def.label ??= ModulesDefinition[key]?.label ?? upperCaseFirst(key)
def.isQueryable = ModulesDefinition[key]?.isQueryable ?? true
const orignalDef = value?.definition ?? ModulesDefinition[key]
if (
!isBoolean(value) &&
(isObject(orignalDef) || !isPresent(value.definition))
) {
value.definition = {
...def,
...orignalDef,
}
}
}
return configModules
}
protected prepareSharedResourcesAndDeps() {
const injectedDependencies = {
[ContainerRegistrationKeys.PG_CONNECTION]: this.#container.resolve(
ContainerRegistrationKeys.PG_CONNECTION
),
[ContainerRegistrationKeys.LOGGER]: this.#container.resolve(
ContainerRegistrationKeys.LOGGER
),
}
const sharedResourcesConfig: ModuleServiceInitializeOptions = {
database: {
clientUrl:
(
injectedDependencies[
ContainerRegistrationKeys.PG_CONNECTION
] as ReturnType<typeof pgConnectionLoader>
)?.client?.config?.connection?.connectionString ??
configManager.config.projectConfig.databaseUrl,
driverOptions: configManager.config.projectConfig.databaseDriverOptions,
debug: configManager.config.projectConfig.databaseLogging ?? false,
schema: configManager.config.projectConfig.databaseSchema,
database: configManager.config.projectConfig.databaseName,
},
}
return { sharedResourcesConfig, injectedDependencies }
}
/**
* Run, Revert or Generate the migrations for the medusa app.
*
* @param moduleNames
* @param linkModules
* @param action
*/
async runModulesMigrations(
{
moduleNames,
action = "run",
}:
| {
moduleNames?: never
action: "run"
}
| {
moduleNames: string[]
action: "revert" | "generate"
} = {
action: "run",
}
): Promise<void> {
const configModules = this.mergeDefaultModules(configManager.config.modules)
const { sharedResourcesConfig, injectedDependencies } =
this.prepareSharedResourcesAndDeps()
const migrationOptions = {
modulesConfig: configModules,
sharedContainer: this.#container,
linkModules: this.#customLinksModules,
sharedResourcesConfig,
injectedDependencies,
}
if (action === "revert") {
await MedusaAppMigrateDown(moduleNames!, migrationOptions)
} else if (action === "run") {
await MedusaAppMigrateUp(migrationOptions)
} else {
await MedusaAppMigrateGenerate(moduleNames!, migrationOptions)
}
}
/**
* Return an instance of the link module migration planner.
*/
async getLinksExecutionPlanner(): Promise<ILinkMigrationsPlanner> {
const configModules = this.mergeDefaultModules(configManager.config.modules)
const { sharedResourcesConfig, injectedDependencies } =
this.prepareSharedResourcesAndDeps()
const migrationOptions = {
modulesConfig: configModules,
sharedContainer: this.#container,
linkModules: this.#customLinksModules,
sharedResourcesConfig,
injectedDependencies,
}
return await MedusaAppGetLinksExecutionPlanner(migrationOptions)
}
/**
* Run the modules loader without taking care of anything else. This is useful for running the loader as a separate action or to re run all modules loaders.
*/
async runModulesLoader(): Promise<void> {
const { sharedResourcesConfig, injectedDependencies } =
this.prepareSharedResourcesAndDeps()
const configModules = this.mergeDefaultModules(configManager.config.modules)
await MedusaApp({
modulesConfig: configModules,
sharedContainer: this.#container,
linkModules: this.#customLinksModules,
sharedResourcesConfig,
injectedDependencies,
loaderOnly: true,
})
}
/**
* Load all modules and bootstrap all the modules and links to be ready to be consumed
* @param config
*/
async load(config = { registerInContainer: true }): Promise<MedusaAppOutput> {
const configModule: ConfigModule = this.#container.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
)
const { sharedResourcesConfig, injectedDependencies } =
this.prepareSharedResourcesAndDeps()
this.#container.register(
ContainerRegistrationKeys.REMOTE_QUERY,
asValue(undefined)
)
this.#container.register(
ContainerRegistrationKeys.REMOTE_LINK,
asValue(undefined)
)
const configModules = this.mergeDefaultModules(configModule.modules)
const medusaApp = await MedusaApp({
workerMode: configModule.projectConfig.workerMode,
modulesConfig: configModules,
sharedContainer: this.#container,
linkModules: this.#customLinksModules,
sharedResourcesConfig,
injectedDependencies,
})
if (!config.registerInContainer) {
return medusaApp
}
container.register(
ContainerRegistrationKeys.REMOTE_LINK,
asValue(medusaApp.link)
)
container.register(
ContainerRegistrationKeys.REMOTE_QUERY,
asValue(medusaApp.query)
)
for (const moduleService of Object.values(medusaApp.modules)) {
const loadedModule = moduleService as LoadedModule
container.register(
loadedModule.__definition.registrationName,
asValue(moduleService)
)
}
// Register all unresolved modules as undefined to be present in the container with undefined value by default
// but still resolvable
for (const moduleDefinition of Object.values(ModulesDefinition)) {
if (!container.hasRegistration(moduleDefinition.registrationName)) {
container.register(
moduleDefinition.registrationName,
asValue(undefined)
)
}
}
return medusaApp
}
}

View File

@@ -2,11 +2,10 @@ import boxen from "boxen"
import chalk from "chalk"
import checkbox from "@inquirer/checkbox"
import { LinkLoader, logger } from "@medusajs/framework"
import { LinkLoader, logger, MedusaAppLoader } from "@medusajs/framework"
import { initializeContainer } from "../loaders"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { getResolvedPlugins } from "../loaders/helpers/resolve-plugins"
import { getLinksExecutionPlanner } from "../loaders/medusa-app"
import { LinkMigrationsPlannerAction } from "@medusajs/types"
import { join } from "path"
@@ -28,7 +27,7 @@ function groupByActionPlan(actionPlan: LinkMigrationsPlannerAction[]) {
* Creates the link description for printing it to the
* console
*
* @param action: LinkMigrationsPlannerAction
* @param action LinkMigrationsPlannerAction
*/
function buildLinkDescription(action: LinkMigrationsPlannerAction) {
const { linkDescriptor } = action
@@ -102,16 +101,15 @@ const main = async function ({ directory }) {
ContainerRegistrationKeys.CONFIG_MODULE
)
const medusaAppLoader = new MedusaAppLoader()
const plugins = getResolvedPlugins(directory, configModule, true) || []
const linksSourcePaths = plugins.map((plugin) =>
join(plugin.resolve, "links")
)
await new LinkLoader(linksSourcePaths).load()
const planner = await getLinksExecutionPlanner({
configModule,
container,
})
const planner = await medusaAppLoader.getLinksExecutionPlanner()
logger.info("Syncing links...")

View File

@@ -1,5 +1,4 @@
import { LinkLoader, logger } from "@medusajs/framework"
import { runMedusaAppMigrations } from "../loaders/medusa-app"
import { LinkLoader, logger, MedusaAppLoader } from "@medusajs/framework"
import { initializeContainer } from "../loaders"
import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils"
import { getResolvedPlugins } from "../loaders/helpers/resolve-plugins"
@@ -51,6 +50,8 @@ const main = async function ({ directory }) {
ContainerRegistrationKeys.CONFIG_MODULE
)
const medusaAppLoader = new MedusaAppLoader()
const plugins = getResolvedPlugins(directory, configModule, true) || []
const linksSourcePaths = plugins.map((plugin) =>
join(plugin.resolve, "links")
@@ -60,9 +61,7 @@ const main = async function ({ directory }) {
if (action === "run") {
logger.info("Running migrations...")
await runMedusaAppMigrations({
configModule,
container,
await medusaAppLoader.runModulesMigrations({
action: "run",
})
@@ -73,10 +72,8 @@ const main = async function ({ directory }) {
logger.info("Reverting migrations...")
try {
await runMedusaAppMigrations({
await medusaAppLoader.runModulesMigrations({
moduleNames: modules,
configModule,
container,
action: "revert",
})
console.log(new Array(TERMINAL_SIZE).join("-"))
@@ -98,10 +95,8 @@ const main = async function ({ directory }) {
} else if (action === "generate") {
logger.info("Generating migrations...")
await runMedusaAppMigrations({
await medusaAppLoader.runModulesMigrations({
moduleNames: modules,
configModule,
container,
action: "generate",
})

View File

@@ -16,12 +16,12 @@ import {
JobLoader,
LinkLoader,
logger,
MedusaAppLoader,
pgConnectionLoader,
SubscriberLoader,
WorkflowLoader,
} from "@medusajs/framework"
import { getResolvedPlugins } from "./helpers/resolve-plugins"
import loadMedusaApp from "./medusa-app"
type Options = {
directory: string
@@ -157,9 +157,7 @@ export default async ({
onApplicationStart,
onApplicationShutdown,
onApplicationPrepareShutdown,
} = await loadMedusaApp({
container,
})
} = await new MedusaAppLoader().load()
const workflowsSourcePaths = plugins.map((p) => join(p.resolve, "workflows"))
const workflowLoader = new WorkflowLoader(workflowsSourcePaths)

View File

@@ -1,314 +0,0 @@
import {
MedusaApp,
MedusaAppGetLinksExecutionPlanner,
MedusaAppMigrateDown,
MedusaAppMigrateGenerate,
MedusaAppMigrateUp,
MedusaAppOptions,
MedusaAppOutput,
ModulesDefinition,
} from "@medusajs/modules-sdk"
import {
CommonTypes,
ConfigModule,
ILinkMigrationsPlanner,
InternalModuleDeclaration,
LoadedModule,
MedusaContainer,
ModuleDefinition,
} from "@medusajs/types"
import {
ContainerRegistrationKeys,
isBoolean,
isObject,
isPresent,
upperCaseFirst,
} from "@medusajs/utils"
import { asValue } from "awilix"
export function mergeDefaultModules(
modulesConfig: CommonTypes.ConfigModule["modules"]
) {
const defaultModules = Object.values(ModulesDefinition).filter(
(definition: ModuleDefinition) => {
return !!definition.defaultPackage
}
)
const configModules = { ...modulesConfig } ?? {}
for (const defaultModule of defaultModules as ModuleDefinition[]) {
configModules[defaultModule.key] ??= defaultModule.defaultModuleDeclaration
}
for (const [key, value] of Object.entries(
configModules as Record<string, InternalModuleDeclaration>
)) {
const def = {} as ModuleDefinition
def.key ??= key
def.registrationName ??= ModulesDefinition[key]?.registrationName ?? key
def.label ??= ModulesDefinition[key]?.label ?? upperCaseFirst(key)
def.isQueryable = ModulesDefinition[key]?.isQueryable ?? true
const orignalDef = value?.definition ?? ModulesDefinition[key]
if (
!isBoolean(value) &&
(isObject(orignalDef) || !isPresent(value.definition))
) {
value.definition = {
...def,
...orignalDef,
}
}
}
return configModules
}
/**
* Run, Revert or Generate the migrations for the medusa app.
*
* @param configModule
* @param container
* @param moduleNames
* @param linkModules
* @param action
*/
export async function runMedusaAppMigrations({
configModule,
container,
moduleNames,
linkModules,
action = "run",
}: {
configModule: {
modules?: CommonTypes.ConfigModule["modules"]
projectConfig: CommonTypes.ConfigModule["projectConfig"]
}
linkModules?: MedusaAppOptions["linkModules"]
container: MedusaContainer
} & (
| {
moduleNames?: never
action: "run"
}
| {
moduleNames: string[]
action: "revert" | "generate"
}
)): Promise<void> {
const injectedDependencies = {
[ContainerRegistrationKeys.PG_CONNECTION]: container.resolve(
ContainerRegistrationKeys.PG_CONNECTION
),
[ContainerRegistrationKeys.LOGGER]: container.resolve(
ContainerRegistrationKeys.LOGGER
),
}
const sharedResourcesConfig = {
database: {
clientUrl:
injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION]?.client
?.config?.connection?.connectionString ??
configModule.projectConfig.databaseUrl,
driverOptions: configModule.projectConfig.databaseDriverOptions,
debug: !!(configModule.projectConfig.databaseLogging ?? false),
},
}
const configModules = mergeDefaultModules(configModule.modules)
const migrationOptions = {
modulesConfig: configModules,
sharedContainer: container,
linkModules,
sharedResourcesConfig,
injectedDependencies,
}
if (action === "revert") {
await MedusaAppMigrateDown(moduleNames!, migrationOptions)
} else if (action === "run") {
await MedusaAppMigrateUp(migrationOptions)
} else {
await MedusaAppMigrateGenerate(moduleNames!, migrationOptions)
}
}
/**
* Return an instance of the link module migration planner.
*
* @param configModule
* @param container
* @param linkModules
*/
export async function getLinksExecutionPlanner({
configModule,
container,
linkModules,
}: {
configModule: {
modules?: CommonTypes.ConfigModule["modules"]
projectConfig: CommonTypes.ConfigModule["projectConfig"]
}
linkModules?: MedusaAppOptions["linkModules"]
container: MedusaContainer
}): Promise<ILinkMigrationsPlanner> {
const injectedDependencies = {
[ContainerRegistrationKeys.PG_CONNECTION]: container.resolve(
ContainerRegistrationKeys.PG_CONNECTION
),
[ContainerRegistrationKeys.LOGGER]: container.resolve(
ContainerRegistrationKeys.LOGGER
),
}
const sharedResourcesConfig = {
database: {
clientUrl:
injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION]?.client
?.config?.connection?.connectionString ??
configModule.projectConfig.databaseUrl,
driverOptions: configModule.projectConfig.databaseDriverOptions,
debug: !!(configModule.projectConfig.databaseLogging ?? false),
},
}
const configModules = mergeDefaultModules(configModule.modules)
const migrationOptions = {
modulesConfig: configModules,
sharedContainer: container,
linkModules,
sharedResourcesConfig,
injectedDependencies,
}
return await MedusaAppGetLinksExecutionPlanner(migrationOptions)
}
export const loadMedusaApp = async (
{
container,
linkModules,
}: {
container: MedusaContainer
linkModules?: MedusaAppOptions["linkModules"]
},
config = { registerInContainer: true }
): Promise<MedusaAppOutput> => {
const injectedDependencies = {
[ContainerRegistrationKeys.PG_CONNECTION]: container.resolve(
ContainerRegistrationKeys.PG_CONNECTION
),
[ContainerRegistrationKeys.LOGGER]: container.resolve(
ContainerRegistrationKeys.LOGGER
),
}
const configModule: ConfigModule = container.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
)
const sharedResourcesConfig = {
database: {
clientUrl: configModule.projectConfig.databaseUrl,
driverOptions: configModule.projectConfig.databaseDriverOptions,
debug: !!(configModule.projectConfig.databaseLogging ?? false),
},
}
container.register(ContainerRegistrationKeys.REMOTE_QUERY, asValue(undefined))
container.register(ContainerRegistrationKeys.REMOTE_LINK, asValue(undefined))
const configModules = mergeDefaultModules(configModule.modules)
const medusaApp = await MedusaApp({
workerMode: configModule.projectConfig.workerMode,
modulesConfig: configModules,
sharedContainer: container,
linkModules,
sharedResourcesConfig,
injectedDependencies,
})
if (!config.registerInContainer) {
return medusaApp
}
container.register(
ContainerRegistrationKeys.REMOTE_LINK,
asValue(medusaApp.link)
)
container.register(
ContainerRegistrationKeys.REMOTE_QUERY,
asValue(medusaApp.query)
)
for (const moduleService of Object.values(medusaApp.modules)) {
const loadedModule = moduleService as LoadedModule
container.register(
loadedModule.__definition.registrationName,
asValue(moduleService)
)
}
// Register all unresolved modules as undefined to be present in the container with undefined value by default
// but still resolvable
for (const moduleDefinition of Object.values(ModulesDefinition)) {
if (!container.hasRegistration(moduleDefinition.registrationName)) {
container.register(moduleDefinition.registrationName, asValue(undefined))
}
}
return medusaApp
}
/**
* Run the modules loader without taking care of anything else. This is useful for running the loader as a separate action or to re run all modules loaders.
*
* @param configModule
* @param container
*/
export async function runModulesLoader({
configModule,
linkModules,
container,
}: {
configModule: {
modules?: CommonTypes.ConfigModule["modules"]
projectConfig: CommonTypes.ConfigModule["projectConfig"]
}
container: MedusaContainer
linkModules?: MedusaAppOptions["linkModules"]
}): Promise<void> {
const injectedDependencies = {
[ContainerRegistrationKeys.PG_CONNECTION]: container.resolve(
ContainerRegistrationKeys.PG_CONNECTION
),
[ContainerRegistrationKeys.LOGGER]: container.resolve(
ContainerRegistrationKeys.LOGGER
),
}
const sharedResourcesConfig = {
database: {
clientUrl: configModule.projectConfig.databaseUrl,
driverOptions: configModule.projectConfig.databaseDriverOptions,
debug: !!(configModule.projectConfig.databaseLogging ?? false),
},
}
const configModules = mergeDefaultModules(configModule.modules)
await MedusaApp({
modulesConfig: configModules,
sharedContainer: container,
linkModules,
sharedResourcesConfig,
injectedDependencies,
loaderOnly: true,
})
}
export default loadMedusaApp