diff --git a/packages/modules-sdk/src/loaders/__mocks__/@plugins/default.ts b/packages/modules-sdk/src/loaders/__mocks__/@plugins/default.ts new file mode 100644 index 0000000000000..ac1a9484ed785 --- /dev/null +++ b/packages/modules-sdk/src/loaders/__mocks__/@plugins/default.ts @@ -0,0 +1,5 @@ +const service = class TestService {} + +export default { + services: [service], +} diff --git a/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-default.ts b/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-default.ts new file mode 100644 index 0000000000000..855f6e0a54c0d --- /dev/null +++ b/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-default.ts @@ -0,0 +1,5 @@ +const service = class TestService {} + +export const defaultExport = { + services: [service], +} diff --git a/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-service.ts b/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-service.ts new file mode 100644 index 0000000000000..d0c074f41fd92 --- /dev/null +++ b/packages/modules-sdk/src/loaders/__mocks__/@plugins/no-service.ts @@ -0,0 +1,3 @@ +export default { + loaders: [], +} diff --git a/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts b/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts new file mode 100644 index 0000000000000..75148a7e8960e --- /dev/null +++ b/packages/modules-sdk/src/loaders/__tests__/module-provider-loader.ts @@ -0,0 +1,94 @@ +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(typeof testService).toEqual("object") + }) + + 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(typeof testService).toEqual("object") + }) + + 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 exports a service." + ) + } + }) + + 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 default export found in plugin @plugins/no-default -- make sure your plugin has a default export." + ) + } + }) +}) diff --git a/packages/modules-sdk/src/loaders/module-provider-loader.ts b/packages/modules-sdk/src/loaders/module-provider-loader.ts index 8da668dc3f978..9a8b6e0083e29 100644 --- a/packages/modules-sdk/src/loaders/module-provider-loader.ts +++ b/packages/modules-sdk/src/loaders/module-provider-loader.ts @@ -1,21 +1,19 @@ -import { Constructor, MedusaContainer } from "@medusajs/types" +import { MedusaContainer, ModuleProvider } from "@medusajs/types" import { isString, promiseAll } from "@medusajs/utils" import { Lifetime, asFunction } from "awilix" -type ModuleProviderExports = { - services: Constructor[] -} - -type ModuleProvider = { - resolve: string | ModuleProviderExports - provider_name?: string - options: Record -} - export async function moduleProviderLoader({ container, providers, registerServiceFn, +}: { + container: MedusaContainer + providers: ModuleProvider[] + registerServiceFn?: ( + klass, + container: MedusaContainer, + pluginDetails: any + ) => Promise }) { if (!providers?.length) { return @@ -53,11 +51,11 @@ export async function loadModuleProvider( if (!loadedProvider) { throw new Error( - `No default export found in plugin ${pluginName} -- make sure your plugin exports a service.` + `No default export found in plugin ${pluginName} -- make sure your plugin has a default export.` ) } - if (!loadedProvider?.services) { + if (!loadedProvider.services?.length) { throw new Error( `No services found in plugin ${provider.resolve} -- make sure your plugin exports a service.` ) @@ -65,12 +63,13 @@ export async function loadModuleProvider( const services = await promiseAll( loadedProvider.services.map(async (service) => { + const name = service.name[0].toLowerCase() + service.name.slice(1) if (registerServiceFn) { // Used to register the specific type of service in the provider await registerServiceFn(service, container, provider.options) } else { container.register({ - [service.name]: asFunction( + [name]: asFunction( (cradle) => new service(cradle, provider.options), { lifetime: service.LIFE_TIME || Lifetime.SCOPED, diff --git a/packages/payment/src/loaders/providers.ts b/packages/payment/src/loaders/providers.ts index e176b00f52476..f2d500469739c 100644 --- a/packages/payment/src/loaders/providers.ts +++ b/packages/payment/src/loaders/providers.ts @@ -1,22 +1,9 @@ -import { AbstractPaymentProcessor } from "@medusajs/medusa" import { moduleProviderLoader } from "@medusajs/modules-sdk" -import { LoaderOptions, ModulesSdkTypes } from "@medusajs/types" +import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types" import { Lifetime, asFunction } from "awilix" -type PaymentModuleProviders = { - providers: { - resolve: string - provider_id: string // e.g. stripe-usd - options: Record - }[] -} - const registrationFn = async (klass, container, pluginOptions) => { - if (!AbstractPaymentProcessor.isPaymentProcessor(klass.prototype)) { - return - } - container.register({ [`payment_provider_${klass.prototype}`]: asFunction( (cradle) => new klass(cradle, pluginOptions), @@ -41,8 +28,7 @@ export default async ({ ( | ModulesSdkTypes.ModuleServiceInitializeOptions | ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions - ) & - PaymentModuleProviders + ) & { providers: ModuleProvider[] } >): Promise => { const pluginProviders = options?.providers?.filter((provider) => provider.resolve) || [] diff --git a/packages/types/src/modules-sdk/index.ts b/packages/types/src/modules-sdk/index.ts index 5a7188cc2347e..c69a3e90a1681 100644 --- a/packages/types/src/modules-sdk/index.ts +++ b/packages/types/src/modules-sdk/index.ts @@ -290,3 +290,13 @@ export interface IModuleService { onApplicationStart?: () => Promise } } + +export type ModuleProviderExports = { + services: Constructor[] +} + +export type ModuleProvider = { + resolve: string | ModuleProviderExports + provider_name?: string + options: Record +}