feat(medusa): Modules initializer (#3352)
This commit is contained in:
committed by
GitHub
parent
8a7421db5b
commit
aa690beed7
@@ -1,4 +1,5 @@
|
||||
export * from "./definitions"
|
||||
export * from "./loaders"
|
||||
export * from "./medusa-module"
|
||||
export * from "./module-helper"
|
||||
export * from "./types"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EOL } from "os"
|
||||
import { AwilixContainer, ClassOrFunctionReturning, Resolver } from "awilix"
|
||||
import { createMedusaContainer } from "medusa-core-utils"
|
||||
import { EOL } from "os"
|
||||
import {
|
||||
ModuleResolution,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
@@ -155,7 +155,7 @@ describe("modules loader", () => {
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`Could not resolve module: TestService. Error: No service found in module. Make sure your module exports at least one service.${EOL}`
|
||||
`Could not resolve module: TestService. Error: No service found in module. Make sure your module exports a service.${EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
@@ -186,7 +186,7 @@ describe("modules loader", () => {
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"No service found in module. Make sure your module exports at least one service."
|
||||
"No service found in module. Make sure your module exports a service."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import MODULE_DEFINITIONS from "../../definitions"
|
||||
import {
|
||||
InternalModuleDeclaration,
|
||||
ModuleDefinition,
|
||||
@@ -5,7 +6,6 @@ import {
|
||||
MODULE_SCOPE,
|
||||
} from "../../types"
|
||||
import { registerModules } from "../register-modules"
|
||||
import MODULE_DEFINITIONS from "../../definitions"
|
||||
|
||||
const RESOLVED_PACKAGE = "@medusajs/test-service-resolved"
|
||||
jest.mock("resolve-cwd", () => jest.fn(() => RESOLVED_PACKAGE))
|
||||
@@ -35,7 +35,7 @@ describe("module definitions loader", () => {
|
||||
it("Resolves module with default definition given empty config", () => {
|
||||
MODULE_DEFINITIONS.push({ ...defaultDefinition })
|
||||
|
||||
const res = registerModules({ modules: {} })
|
||||
const res = registerModules({})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -54,9 +54,7 @@ describe("module definitions loader", () => {
|
||||
it("Resolves module with no resolution path when given false", () => {
|
||||
MODULE_DEFINITIONS.push({ ...defaultDefinition })
|
||||
|
||||
const res = registerModules({
|
||||
modules: { [defaultDefinition.key]: false },
|
||||
})
|
||||
const res = registerModules({ [defaultDefinition.key]: false })
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -72,9 +70,7 @@ describe("module definitions loader", () => {
|
||||
MODULE_DEFINITIONS.push({ ...defaultDefinition, isRequired: true })
|
||||
|
||||
try {
|
||||
registerModules({
|
||||
modules: { [defaultDefinition.key]: false },
|
||||
})
|
||||
registerModules({ [defaultDefinition.key]: false })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
`Module: ${defaultDefinition.label} is required`
|
||||
@@ -90,9 +86,7 @@ describe("module definitions loader", () => {
|
||||
|
||||
MODULE_DEFINITIONS.push(definition)
|
||||
|
||||
const res = registerModules({
|
||||
modules: {},
|
||||
})
|
||||
const res = registerModules({})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -113,9 +107,7 @@ describe("module definitions loader", () => {
|
||||
MODULE_DEFINITIONS.push({ ...defaultDefinition })
|
||||
|
||||
const res = registerModules({
|
||||
modules: {
|
||||
[defaultDefinition.key]: defaultDefinition.defaultPackage,
|
||||
},
|
||||
[defaultDefinition.key]: defaultDefinition.defaultPackage,
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
@@ -137,13 +129,11 @@ describe("module definitions loader", () => {
|
||||
MODULE_DEFINITIONS.push({ ...defaultDefinition })
|
||||
|
||||
const res = registerModules({
|
||||
modules: {
|
||||
[defaultDefinition.key]: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
} as InternalModuleDeclaration,
|
||||
},
|
||||
[defaultDefinition.key]: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
} as InternalModuleDeclaration,
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
@@ -164,10 +154,8 @@ describe("module definitions loader", () => {
|
||||
MODULE_DEFINITIONS.push({ ...defaultDefinition })
|
||||
|
||||
const res = registerModules({
|
||||
modules: {
|
||||
[defaultDefinition.key]: {
|
||||
options: { test: 123 },
|
||||
},
|
||||
[defaultDefinition.key]: {
|
||||
options: { test: 123 },
|
||||
},
|
||||
} as any)
|
||||
|
||||
@@ -189,13 +177,11 @@ describe("module definitions loader", () => {
|
||||
MODULE_DEFINITIONS.push({ ...defaultDefinition })
|
||||
|
||||
const res = registerModules({
|
||||
modules: {
|
||||
[defaultDefinition.key]: {
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
options: { test: 123 },
|
||||
scope: "internal",
|
||||
resources: "isolated",
|
||||
},
|
||||
[defaultDefinition.key]: {
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
options: { test: 123 },
|
||||
scope: "internal",
|
||||
resources: "isolated",
|
||||
},
|
||||
} as any)
|
||||
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import resolveCwd from "resolve-cwd"
|
||||
|
||||
import MODULE_DEFINITIONS from "../definitions"
|
||||
import {
|
||||
MedusaModuleConfig,
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
ModuleDefinition,
|
||||
ModuleResolution,
|
||||
MODULE_SCOPE,
|
||||
} from "../types"
|
||||
import MODULE_DEFINITIONS from "../definitions"
|
||||
|
||||
export const registerModules = ({
|
||||
modules,
|
||||
}: MedusaModuleConfig): Record<string, ModuleResolution> => {
|
||||
export const registerModules = (
|
||||
modules?: Record<
|
||||
string,
|
||||
| false
|
||||
| string
|
||||
| Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
>
|
||||
): Record<string, ModuleResolution> => {
|
||||
const moduleResolutions = {} as Record<string, ModuleResolution>
|
||||
const projectModules = modules ?? {}
|
||||
|
||||
@@ -26,10 +31,32 @@ export const registerModules = ({
|
||||
|
||||
moduleResolutions[definition.key] = getInternalModuleResolution(
|
||||
definition,
|
||||
projectModules[definition.key] as
|
||||
| InternalModuleDeclaration
|
||||
| false
|
||||
| string
|
||||
customConfig as InternalModuleDeclaration
|
||||
)
|
||||
}
|
||||
|
||||
return moduleResolutions
|
||||
}
|
||||
|
||||
export const registerMedusaModule = (
|
||||
moduleKey: string,
|
||||
moduleDeclaration: InternalModuleDeclaration | ExternalModuleDeclaration
|
||||
): Record<string, ModuleResolution> => {
|
||||
const moduleResolutions = {} as Record<string, ModuleResolution>
|
||||
|
||||
for (const definition of MODULE_DEFINITIONS) {
|
||||
if (definition.key !== moduleKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (moduleDeclaration.scope === MODULE_SCOPE.EXTERNAL) {
|
||||
// TODO: getExternalModuleResolution(...)a
|
||||
throw new Error("External Modules are not supported yet.")
|
||||
}
|
||||
|
||||
moduleResolutions[definition.key] = getInternalModuleResolution(
|
||||
definition,
|
||||
moduleDeclaration as InternalModuleDeclaration
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function loadInternalModule(
|
||||
|
||||
return {
|
||||
error: new Error(
|
||||
"No service found in module. Make sure your module exports at least one service."
|
||||
"No service found in module. Make sure your module exports a service."
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -112,3 +112,15 @@ export async function loadInternalModule(
|
||||
"module"
|
||||
)
|
||||
}
|
||||
|
||||
export async function loadModuleMigrations(
|
||||
resolution: ModuleResolution
|
||||
): Promise<[Function | undefined, Function | undefined]> {
|
||||
let loadedModule: ModuleExports
|
||||
try {
|
||||
loadedModule = (await import(resolution.resolutionPath as string)).default
|
||||
return [loadedModule.runMigrations, loadedModule.revertMigration]
|
||||
} catch {
|
||||
return [undefined, undefined]
|
||||
}
|
||||
}
|
||||
|
||||
108
packages/modules-sdk/src/medusa-module.ts
Normal file
108
packages/modules-sdk/src/medusa-module.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { asValue } from "awilix"
|
||||
import { createMedusaContainer } from "medusa-core-utils"
|
||||
import { moduleLoader, registerMedusaModule } from "./loaders"
|
||||
import { loadModuleMigrations } from "./loaders/utils"
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
} from "./types"
|
||||
|
||||
const logger: any = {
|
||||
log: (a) => console.log(a),
|
||||
info: (a) => console.log(a),
|
||||
warn: (a) => console.warn(a),
|
||||
error: (a) => console.error(a),
|
||||
}
|
||||
|
||||
export class MedusaModule {
|
||||
public static async bootstrap(
|
||||
moduleKey: string,
|
||||
defaultPath: string,
|
||||
declaration?: InternalModuleDeclaration | ExternalModuleDeclaration,
|
||||
injectedDependencies?: Record<string, any>
|
||||
): Promise<{
|
||||
[key: string]: any
|
||||
}> {
|
||||
let modDeclaration = declaration
|
||||
if (declaration?.scope !== MODULE_SCOPE.EXTERNAL) {
|
||||
modDeclaration = {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: defaultPath,
|
||||
options: declaration,
|
||||
}
|
||||
}
|
||||
|
||||
const container = createMedusaContainer()
|
||||
|
||||
if (injectedDependencies) {
|
||||
for (const service in injectedDependencies) {
|
||||
container.register(service, asValue(injectedDependencies[service]))
|
||||
}
|
||||
}
|
||||
|
||||
const moduleResolutions = registerMedusaModule(moduleKey, modDeclaration!)
|
||||
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
const services = {}
|
||||
|
||||
for (const resolution of Object.values(moduleResolutions)) {
|
||||
const registrationName = resolution.definition.registrationName
|
||||
|
||||
services[registrationName] = container.resolve(registrationName)
|
||||
}
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
public static async migrateUp(
|
||||
moduleKey: string,
|
||||
modulePath: string,
|
||||
options?: Record<string, any>
|
||||
): Promise<void> {
|
||||
const moduleResolutions = registerMedusaModule(moduleKey, {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
})
|
||||
|
||||
for (const mod in moduleResolutions) {
|
||||
const [migrateUp] = await loadModuleMigrations(moduleResolutions[mod])
|
||||
|
||||
if (typeof migrateUp === "function") {
|
||||
await migrateUp({
|
||||
options,
|
||||
logger,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async migrateDown(
|
||||
moduleKey: string,
|
||||
modulePath: string,
|
||||
options?: Record<string, any>
|
||||
): Promise<void> {
|
||||
const moduleResolutions = registerMedusaModule(moduleKey, {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: modulePath,
|
||||
options,
|
||||
})
|
||||
|
||||
for (const mod in moduleResolutions) {
|
||||
const [, migrateDown] = await loadModuleMigrations(moduleResolutions[mod])
|
||||
|
||||
if (typeof migrateDown === "function") {
|
||||
await migrateDown({
|
||||
options,
|
||||
logger,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,9 +68,9 @@ export type ModuleDefinition = {
|
||||
| ExternalModuleDeclaration
|
||||
}
|
||||
|
||||
export type LoaderOptions = {
|
||||
export type LoaderOptions<TOptions = Record<string, unknown>> = {
|
||||
container: MedusaContainer
|
||||
options?: Record<string, unknown>
|
||||
options?: TOptions
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
@@ -89,13 +89,12 @@ export type ModuleExports = {
|
||||
loaders?: ModuleLoaderFunction[]
|
||||
migrations?: any[]
|
||||
models?: Constructor<any>[]
|
||||
}
|
||||
|
||||
export type MedusaModuleConfig = {
|
||||
modules?: Record<
|
||||
string,
|
||||
| false
|
||||
| string
|
||||
| Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
>
|
||||
runMigrations?(
|
||||
options: LoaderOptions,
|
||||
moduleDeclaration: InternalModuleDeclaration
|
||||
): Promise<void>
|
||||
revertMigration?(
|
||||
options: LoaderOptions,
|
||||
moduleDeclaration: InternalModuleDeclaration
|
||||
): Promise<void>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user