feat(modules-sdk): Module provider plugin loader (#6286)
This commit is contained in:
1 parent
45134e4d11
commit
3a103f0c36
13 files changed
+255
-6
No files matched your search
@@ -32,6 +32,7 @@ export default async ({
|
|||||||
options?.providers?.map((provider) => [provider.name, provider.scopes]) ??
|
options?.providers?.map((provider) => [provider.name, provider.scopes]) ??
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
// if(options?.providers?.length) {
|
// if(options?.providers?.length) {
|
||||||
// TODO: implement plugin provider registration
|
// 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-loader"
|
||||||
|
export * from "./module-provider-loader"
|
||||||
export * from "./register-modules"
|
export * from "./register-modules"
|
||||||
|
|
||||||
@@ -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 { IPaymentModuleService } from "@medusajs/types"
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
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 { 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)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
export * from "./connection"
|
export * from "./connection"
|
||||||
export * from "./container"
|
export * from "./container"
|
||||||
|
export * from "./providers"
|
||||||
|
|
||||||
@@ -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 loadConnection from "./loaders/connection"
|
||||||
import loadContainer from "./loaders/container"
|
import loadContainer from "./loaders/container"
|
||||||
|
import loadProviders from "./loaders/providers"
|
||||||
|
|
||||||
import { Modules } from "@medusajs/modules-sdk"
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||||
@@ -24,7 +25,7 @@ export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const service = PaymentModuleService
|
const service = PaymentModuleService
|
||||||
const loaders = [loadContainer, loadConnection] as any
|
const loaders = [loadContainer, loadConnection, loadProviders] as any
|
||||||
|
|
||||||
export const moduleDefinition: ModuleExports = {
|
export const moduleDefinition: ModuleExports = {
|
||||||
service,
|
service,
|
||||||
|
|||||||
@@ -290,3 +290,13 @@ export interface IModuleService {
|
|||||||
onApplicationStart?: () => Promise<void>
|
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 "./transaction"
|
||||||
export * from "./upper-case-first"
|
export * from "./upper-case-first"
|
||||||
export * from "./wrap-handler"
|
export * from "./wrap-handler"
|
||||||
|
|
||||||
Reference in new issue
Block a user