diff --git a/packages/core/medusa-test-utils/src/medusa-test-runner-utils/use-db.ts b/packages/core/medusa-test-utils/src/medusa-test-runner-utils/use-db.ts index b0f424bd98..ad617127a9 100644 --- a/packages/core/medusa-test-utils/src/medusa-test-runner-utils/use-db.ts +++ b/packages/core/medusa-test-utils/src/medusa-test-runner-utils/use-db.ts @@ -7,15 +7,13 @@ export async function initDb({ env = {} }: { env?: Record }) { } 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 }) { }) 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) diff --git a/packages/core/medusa-test-utils/src/medusa-test-runner.ts b/packages/core/medusa-test-utils/src/medusa-test-runner.ts index 084d7aaf3d..9e6bac5fef 100644 --- a/packages/core/medusa-test-utils/src/medusa-test-runner.ts +++ b/packages/core/medusa-test-utils/src/medusa-test-runner.ts @@ -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 diff --git a/packages/framework/framework/src/index.ts b/packages/framework/framework/src/index.ts index 6997855866..d0b0b5ddef 100644 --- a/packages/framework/framework/src/index.ts +++ b/packages/framework/framework/src/index.ts @@ -8,3 +8,4 @@ export * from "./links" export * from "./jobs" export * from "./feature-flags" export * from "./workflows" +export * from "./medusa-app-loader" diff --git a/packages/framework/framework/src/medusa-app-loader.ts b/packages/framework/framework/src/medusa-app-loader.ts new file mode 100644 index 0000000000..53f0c8b528 --- /dev/null +++ b/packages/framework/framework/src/medusa-app-loader.ts @@ -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 + )) { + 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 + )?.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 { + 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 { + 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 { + 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 { + 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 + } +} diff --git a/packages/medusa/src/commands/links.ts b/packages/medusa/src/commands/links.ts index 47fa930086..c5feba7708 100644 --- a/packages/medusa/src/commands/links.ts +++ b/packages/medusa/src/commands/links.ts @@ -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...") diff --git a/packages/medusa/src/commands/migrate.ts b/packages/medusa/src/commands/migrate.ts index a29b77c303..36f475167d 100644 --- a/packages/medusa/src/commands/migrate.ts +++ b/packages/medusa/src/commands/migrate.ts @@ -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", }) diff --git a/packages/medusa/src/loaders/index.ts b/packages/medusa/src/loaders/index.ts index d63e8a98f2..2ac0a2cb38 100644 --- a/packages/medusa/src/loaders/index.ts +++ b/packages/medusa/src/loaders/index.ts @@ -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) diff --git a/packages/medusa/src/loaders/medusa-app.ts b/packages/medusa/src/loaders/medusa-app.ts deleted file mode 100644 index ffee5b7e08..0000000000 --- a/packages/medusa/src/loaders/medusa-app.ts +++ /dev/null @@ -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 - )) { - 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 { - 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 { - 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 => { - 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 { - 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