feat(modules-sdk): Module provider plugin loader (#6286)
This commit is contained in:
@@ -32,6 +32,7 @@ export default async ({
|
||||
options?.providers?.map((provider) => [provider.name, provider.scopes]) ??
|
||||
[]
|
||||
)
|
||||
|
||||
// if(options?.providers?.length) {
|
||||
// TODO: implement plugin provider registration
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const service = class TestService {}
|
||||
|
||||
export default {
|
||||
services: [service],
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const service = class TestService {}
|
||||
|
||||
export const defaultExport = {
|
||||
services: [service],
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
loaders: [],
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import { Lifetime, asFunction } from "awilix"
|
||||
import { moduleProviderLoader } from "../module-provider-loader"
|
||||
|
||||
const logger = {
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
} as any
|
||||
|
||||
describe("modules loader", () => {
|
||||
let container
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
container = createMedusaContainer()
|
||||
})
|
||||
|
||||
it("should register the provider service", async () => {
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@plugins/default",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
await moduleProviderLoader({ container, providers: moduleProviders })
|
||||
|
||||
const testService = container.resolve("testService")
|
||||
expect(testService).toBeTruthy()
|
||||
expect(testService.constructor.name).toEqual("TestService")
|
||||
})
|
||||
|
||||
it("should register the provider service with custom register fn", async () => {
|
||||
const fn = async (klass, container, details) => {
|
||||
container.register({
|
||||
[`testServiceCustomRegistration`]: asFunction(
|
||||
(cradle) => new klass(cradle, details.options),
|
||||
{
|
||||
lifetime: Lifetime.SINGLETON,
|
||||
}
|
||||
),
|
||||
})
|
||||
}
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@plugins/default",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
await moduleProviderLoader({
|
||||
container,
|
||||
providers: moduleProviders,
|
||||
registerServiceFn: fn,
|
||||
})
|
||||
|
||||
const testService = container.resolve("testServiceCustomRegistration")
|
||||
expect(testService).toBeTruthy()
|
||||
expect(testService.constructor.name).toEqual("TestService")
|
||||
})
|
||||
|
||||
it("should log the errors if no service is defined", async () => {
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@plugins/no-service",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
try {
|
||||
await moduleProviderLoader({ container, providers: moduleProviders })
|
||||
} catch (error) {
|
||||
expect(error.message).toBe(
|
||||
"No services found in plugin @plugins/no-service -- make sure your plugin has a default export of services."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw if no default export is defined", async () => {
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@plugins/no-default",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
try {
|
||||
await moduleProviderLoader({ container, providers: moduleProviders })
|
||||
} catch (error) {
|
||||
expect(error.message).toBe(
|
||||
"No services found in plugin @plugins/no-default -- make sure your plugin has a default export of services."
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from "./module-loader"
|
||||
export * from "./module-provider-loader"
|
||||
export * from "./register-modules"
|
||||
|
||||
|
||||
80
packages/modules-sdk/src/loaders/module-provider-loader.ts
Normal file
80
packages/modules-sdk/src/loaders/module-provider-loader.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { MedusaContainer, ModuleProvider } from "@medusajs/types"
|
||||
import { isString, lowerCaseFirst, promiseAll } from "@medusajs/utils"
|
||||
import { Lifetime, asFunction } from "awilix"
|
||||
|
||||
export async function moduleProviderLoader({
|
||||
container,
|
||||
providers,
|
||||
registerServiceFn,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
providers: ModuleProvider[]
|
||||
registerServiceFn?: (
|
||||
klass,
|
||||
container: MedusaContainer,
|
||||
pluginDetails: any
|
||||
) => Promise<void>
|
||||
}) {
|
||||
if (!providers?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
await promiseAll(
|
||||
providers.map(async (pluginDetails) => {
|
||||
await loadModuleProvider(container, pluginDetails, registerServiceFn)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export async function loadModuleProvider(
|
||||
container: MedusaContainer,
|
||||
provider: ModuleProvider,
|
||||
registerServiceFn?: (klass, container, pluginDetails) => Promise<void>
|
||||
) {
|
||||
let loadedProvider: any
|
||||
|
||||
const pluginName = provider.resolve ?? provider.provider_name ?? ""
|
||||
|
||||
try {
|
||||
loadedProvider = provider.resolve
|
||||
|
||||
if (isString(provider.resolve)) {
|
||||
loadedProvider = await import(provider.resolve)
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Unable to find plugin ${pluginName} -- perhaps you need to install its package?`
|
||||
)
|
||||
}
|
||||
|
||||
loadedProvider = (loadedProvider as any).default ?? loadedProvider
|
||||
|
||||
if (!loadedProvider?.services?.length) {
|
||||
throw new Error(
|
||||
`No services found in plugin ${provider.resolve} -- make sure your plugin has a default export of services.`
|
||||
)
|
||||
}
|
||||
|
||||
const services = await promiseAll(
|
||||
loadedProvider.services.map(async (service) => {
|
||||
const name = lowerCaseFirst(service.name)
|
||||
if (registerServiceFn) {
|
||||
// Used to register the specific type of service in the provider
|
||||
await registerServiceFn(service, container, provider.options)
|
||||
} else {
|
||||
container.register({
|
||||
[name]: asFunction(
|
||||
(cradle) => new service(cradle, provider.options),
|
||||
{
|
||||
lifetime: service.LIFE_TIME || Lifetime.SCOPED,
|
||||
}
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return service
|
||||
})
|
||||
)
|
||||
|
||||
return services
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { initialize } from "../../../../src/initialize"
|
||||
import { DB_URL, MikroOrmWrapper } from "../../../utils"
|
||||
import { createPaymentCollections } from "../../../__fixtures__/payment-collection"
|
||||
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { initialize } from "../../../../src/initialize"
|
||||
import { createPaymentCollections } from "../../../__fixtures__/payment-collection"
|
||||
import { DB_URL, MikroOrmWrapper } from "../../../utils"
|
||||
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from "./connection"
|
||||
export * from "./container"
|
||||
export * from "./providers"
|
||||
|
||||
|
||||
41
packages/payment/src/loaders/providers.ts
Normal file
41
packages/payment/src/loaders/providers.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { moduleProviderLoader } from "@medusajs/modules-sdk"
|
||||
|
||||
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { Lifetime, asFunction } from "awilix"
|
||||
|
||||
const registrationFn = async (klass, container, pluginOptions) => {
|
||||
container.register({
|
||||
[`payment_provider_${klass.prototype}`]: asFunction(
|
||||
(cradle) => new klass(cradle, pluginOptions),
|
||||
{
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
}
|
||||
),
|
||||
})
|
||||
|
||||
container.registerAdd(
|
||||
"payment_providers",
|
||||
asFunction((cradle) => new klass(cradle, pluginOptions), {
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export default async ({
|
||||
container,
|
||||
options,
|
||||
}: LoaderOptions<
|
||||
(
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
) & { providers: ModuleProvider[] }
|
||||
>): Promise<void> => {
|
||||
const pluginProviders =
|
||||
options?.providers?.filter((provider) => provider.resolve) || []
|
||||
|
||||
await moduleProviderLoader({
|
||||
container,
|
||||
providers: pluginProviders,
|
||||
registerServiceFn: registrationFn,
|
||||
})
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { PaymentModuleService } from "@services"
|
||||
|
||||
import loadConnection from "./loaders/connection"
|
||||
import loadContainer from "./loaders/container"
|
||||
import loadProviders from "./loaders/providers"
|
||||
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
@@ -24,7 +25,7 @@ export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
|
||||
)
|
||||
|
||||
const service = PaymentModuleService
|
||||
const loaders = [loadContainer, loadConnection] as any
|
||||
const loaders = [loadContainer, loadConnection, loadProviders] as any
|
||||
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
|
||||
@@ -290,3 +290,13 @@ export interface IModuleService {
|
||||
onApplicationStart?: () => Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
export type ModuleProviderExports = {
|
||||
services: Constructor<any>[]
|
||||
}
|
||||
|
||||
export type ModuleProvider = {
|
||||
resolve: string | ModuleProviderExports
|
||||
provider_name?: string
|
||||
options: Record<string, unknown>
|
||||
}
|
||||
|
||||
@@ -41,3 +41,4 @@ export * from "./to-pascal-case"
|
||||
export * from "./transaction"
|
||||
export * from "./upper-case-first"
|
||||
export * from "./wrap-handler"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user