Feat(modules-sdk,inventory,stock-location): modules isolated connection (#3329)

* feat: scoped container for modules
This commit is contained in:
Carlos R. L. Rodrigues
2023-03-15 12:09:45 -03:00
committed by GitHub
parent 8e78c533c4
commit 77d46220c2
65 changed files with 1116 additions and 1011 deletions
@@ -75,7 +75,7 @@ export default async (req, res) => {
.withTransaction(transactionManager)
.removeLocation(id)
await stockLocationService.withTransaction(transactionManager).delete(id)
await stockLocationService.delete(id)
if (inventoryService) {
await Promise.all([
@@ -1,11 +1,15 @@
import glob from "glob"
import path from "path"
import fs from "fs"
import { isString } from "lodash"
import { sync as existsSync } from "fs-exists-cached"
import { getConfigFile, createRequireFromPath } from "medusa-core-utils"
import { handleConfigError } from "../../loaders/config"
import { registerModules } from "@medusajs/modules-sdk"
import fs from "fs"
import { sync as existsSync } from "fs-exists-cached"
import glob from "glob"
import { isString } from "lodash"
import {
createRequireFromPath,
getConfigFile,
isDefined,
} from "medusa-core-utils"
import path from "path"
import { handleConfigError } from "../../loaders/config"
function createFileContentHash(path, files) {
return path + files
@@ -104,7 +108,7 @@ export function getInternalModules(configModule) {
let loadedModule = null
try {
loadedModule = require(moduleResolution.moduleDeclaration.resolve).default
loadedModule = require(moduleResolution.resolutionPath).default
} catch (error) {
console.log("Error loading Module", error)
continue
@@ -179,15 +183,12 @@ export const getEnabledMigrations = (migrationDirs, isFlagEnabled) => {
return allMigrations
.map((file) => {
const loaded = require(file)
if (
typeof loaded.featureFlag === "undefined" ||
isFlagEnabled(loaded.featureFlag)
) {
return file
if (!isDefined(loaded.featureFlag) || isFlagEnabled(loaded.featureFlag)) {
delete loaded.featureFlag
return Object.values(loaded)
}
return false
})
.flat()
.filter(Boolean)
}
@@ -201,22 +202,17 @@ export const getModuleMigrations = (configModule, isFlagEnabled) => {
const isolatedMigrations = {}
const moduleMigrations = (mod.migrations ?? [])
.map((migrations) => {
const all = []
for (const migration of Object.values(migrations)) {
// TODO: revisit how Modules export their migration entrypoints up/down
if (["up", "down"].includes(migration.name)) {
isolatedMigrations[migration.name] = migration
} else if (
typeof migration.featureFlag === "undefined" ||
isFlagEnabled(migration.featureFlag)
) {
all.push(migration)
}
.map((migration) => {
if (
!isDefined(migration.featureFlag) ||
isFlagEnabled(migration.featureFlag)
) {
delete migration.featureFlag
return Object.values(migration)
}
return all
})
.flat()
.filter(Boolean)
allModules.push({
moduleDeclaration: loadedModule.moduleDeclaration,
+1 -2
View File
@@ -36,7 +36,6 @@ const config = {
admin_cors: "",
store_cors: "",
},
moduleResolutions,
}
const testApp = express()
@@ -70,7 +69,7 @@ featureFlagLoader(config)
servicesLoader({ container, configModule: config })
strategiesLoader({ container, configModule: config })
passportLoader({ app: testApp, container, configModule: config })
moduleLoader({ container, configModule: config })
moduleLoader({ container, moduleResolutions })
testApp.use((req, res, next) => {
req.scope = container.createScope()
@@ -1,15 +1,12 @@
import { EntityManager } from "typeorm"
import { FindConfig } from "../../types/common"
import {
StockLocationDTO,
FilterableStockLocationProps,
CreateStockLocationInput,
FilterableStockLocationProps,
StockLocationDTO,
UpdateStockLocationInput,
} from "../../types/stock-location"
export interface IStockLocationService {
withTransaction(transactionManager?: EntityManager): this
list(
selector: FilterableStockLocationProps,
config?: FindConfig<StockLocationDTO>
@@ -1,17 +1,15 @@
import {
asFunction,
asValue,
AwilixContainer,
ClassOrFunctionReturning,
createContainer,
Resolver,
} from "awilix"
import { mkdirSync, rmSync, writeFileSync } from "fs"
import { resolve } from "path"
import Logger from "../logger"
import { registerServices, registerStrategies } from "../plugins"
import { MedusaContainer } from "../../types/global"
import { Connection, EntityManager } from "typeorm";
import { DataSource, EntityManager } from "typeorm"
import { createMedusaContainer } from "medusa-core-utils"
// ***** TEMPLATES *****
const buildServiceTemplate = (name: string): string => {
@@ -37,10 +35,10 @@ const buildBatchJobStrategyTemplate = (name: string, type: string): string => {
class ${name}BatchStrategy extends AbstractBatchJobStrategy{
static identifier = '${name}-identifier';
static batchType = '${type}';
manager_
transactionManager_
validateContext(context) {
throw new Error("Method not implemented.")
}
@@ -57,7 +55,7 @@ const buildBatchJobStrategyTemplate = (name: string, type: string): string => {
throw new Error("Method not implemented.")
}
}
export default ${name}BatchStrategy
`
}
@@ -65,7 +63,7 @@ const buildBatchJobStrategyTemplate = (name: string, type: string): string => {
const buildPriceSelectionStrategyTemplate = (name: string): string => {
return `
import { AbstractPriceSelectionStrategy } from "../../../../interfaces/price-selection-strategy"
class ${name}PriceSelectionStrategy extends AbstractPriceSelectionStrategy {
withTransaction() {
throw new Error("Method not implemented.");
@@ -74,7 +72,7 @@ const buildPriceSelectionStrategyTemplate = (name: string): string => {
throw new Error("Method not implemented.");
}
}
export default ${name}PriceSelectionStrategy
`
}
@@ -111,32 +109,10 @@ function asArray(
// ***** TESTS *****
describe("plugins loader", () => {
const container = createContainer() as MedusaContainer
container.registerAdd = function (
this: MedusaContainer,
name: string,
registration: typeof asFunction | typeof asValue
): MedusaContainer {
const storeKey = name + "_STORE"
if (this.registrations[storeKey] === undefined) {
this.register(storeKey, asValue([] as Resolver<unknown>[]))
}
const store = this.resolve(storeKey) as (
| ClassOrFunctionReturning<unknown>
| Resolver<unknown>
)[]
if (this.registrations[name] === undefined) {
this.register(name, asArray(store))
}
store.unshift(registration)
return this
}.bind(container)
const container = createMedusaContainer()
container.register("logger", asValue(Logger))
container.register("manager", asValue(new EntityManager({} as Connection)))
container.register("manager", asValue(new EntityManager({} as DataSource)))
const pluginsDetails = {
resolve: resolve(__dirname, "__pluginsLoaderTest__"),
@@ -207,8 +183,9 @@ describe("plugins loader", () => {
})
it("registers price selection strategy", () => {
const priceSelectionStrategy =
container.resolve("priceSelectionStrategy") as (...args: unknown[]) => any
const priceSelectionStrategy = container.resolve(
"priceSelectionStrategy"
) as (...args: unknown[]) => any
expect(priceSelectionStrategy).toBeTruthy()
expect(priceSelectionStrategy.constructor.name).toBe(
@@ -217,8 +194,9 @@ describe("plugins loader", () => {
})
it("registers tax calculation strategy", () => {
const taxCalculationStrategy =
container.resolve("taxCalculationStrategy") as (...args: unknown[]) => any
const taxCalculationStrategy = container.resolve(
"taxCalculationStrategy"
) as (...args: unknown[]) => any
expect(taxCalculationStrategy).toBeTruthy()
expect(taxCalculationStrategy.constructor.name).toBe(
@@ -227,8 +205,9 @@ describe("plugins loader", () => {
})
it("registers batch job strategies as single array", () => {
const batchJobStrategies =
container.resolve("batchJobStrategies") as (...args: unknown[]) => any
const batchJobStrategies = container.resolve("batchJobStrategies") as (
...args: unknown[]
) => any
expect(batchJobStrategies).toBeTruthy()
expect(Array.isArray(batchJobStrategies)).toBeTruthy()
@@ -236,8 +215,9 @@ describe("plugins loader", () => {
})
it("registers batch job strategies by type and only keep the last", () => {
const batchJobStrategy =
container.resolve("batchType_type-1") as (...args: unknown[]) => any
const batchJobStrategy = container.resolve("batchType_type-1") as (
...args: unknown[]
) => any
expect(batchJobStrategy).toBeTruthy()
expect(batchJobStrategy.constructor.name).toBe("testBatch2BatchStrategy")
-4
View File
@@ -1,7 +1,6 @@
import { getConfigFile } from "medusa-core-utils"
import { ConfigModule } from "../types/global"
import logger from "./logger"
import { registerModules } from "@medusajs/modules-sdk"
const isProduction = ["production", "prod"].includes(process.env.NODE_ENV || "")
@@ -65,8 +64,6 @@ export default (rootDirectory: string): ConfigModule => {
)
}
const moduleResolutions = registerModules(configModule)
return {
projectConfig: {
jwt_secret: jwt_secret ?? "supersecret",
@@ -74,7 +71,6 @@ export default (rootDirectory: string): ConfigModule => {
...configModule?.projectConfig,
},
modules: configModule.modules ?? {},
moduleResolutions,
featureFlags: configModule?.featureFlags ?? {},
plugins: configModule?.plugins ?? [],
}
+9 -43
View File
@@ -1,11 +1,4 @@
import {
asFunction,
asValue,
AwilixContainer,
createContainer,
Resolver,
} from "awilix"
import { ClassOrFunctionReturning } from "awilix/lib/container"
import { asValue } from "awilix"
import { Express, NextFunction, Request, Response } from "express"
import { track } from "medusa-telemetry"
import { EOL } from "os"
@@ -30,7 +23,8 @@ import servicesLoader from "./services"
import strategiesLoader from "./strategies"
import subscribersLoader from "./subscribers"
import { moduleLoader } from "@medusajs/modules-sdk"
import { moduleLoader, registerModules } from "@medusajs/modules-sdk"
import { createMedusaContainer } from "medusa-core-utils"
type Options = {
directory: string
@@ -49,32 +43,9 @@ export default async ({
}> => {
const configModule = loadConfig(rootDirectory)
const container = createContainer() as MedusaContainer
const container = createMedusaContainer()
container.register("configModule", asValue(configModule))
container.registerAdd = function (
this: MedusaContainer,
name: string,
registration: typeof asFunction | typeof asValue
) {
const storeKey = name + "_STORE"
if (this.registrations[storeKey] === undefined) {
this.register(storeKey, asValue([] as Resolver<unknown>[]))
}
const store = this.resolve(storeKey) as (
| ClassOrFunctionReturning<unknown>
| Resolver<unknown>
)[]
if (this.registrations[name] === undefined) {
this.register(name, asArray(store))
}
store.unshift(registration)
return this
}.bind(container)
// Add additional information to context of request
expressApp.use((req: Request, res: Response, next: NextFunction) => {
const ipAddress = requestIp.getClientIp(req) as string
@@ -118,7 +89,11 @@ export default async ({
const modulesActivity = Logger.activity(`Initializing modules${EOL}`)
track("MODULES_INIT_STARTED")
await moduleLoader({ container, configModule, logger: Logger })
await moduleLoader({
container,
moduleResolutions: registerModules(configModule),
logger: Logger,
})
const modAct = Logger.success(modulesActivity, "Modules initialized") || {}
track("MODULES_INIT_COMPLETED", { duration: modAct.duration })
@@ -200,12 +175,3 @@ export default async ({
return { container, dbConnection, app: expressApp }
}
function asArray(
resolvers: (ClassOrFunctionReturning<unknown> | Resolver<unknown>)[]
): { resolve: (container: AwilixContainer) => unknown[] } {
return {
resolve: (container: AwilixContainer) =>
resolvers.map((resolver) => container.build(resolver)),
}
}
+4 -13
View File
@@ -1,8 +1,5 @@
import {
ConfigurableModuleDeclaration,
ModuleResolution,
} from "@medusajs/modules-sdk"
import { AwilixContainer } from "awilix"
import { InternalModuleDeclaration } from "@medusajs/modules-sdk"
import { MedusaContainer as coreMedusaContainer } from "medusa-core-utils"
import { Request } from "express"
import { LoggerOptions } from "typeorm"
import { Logger as _Logger } from "winston"
@@ -34,9 +31,7 @@ export type ClassConstructor<T> = {
new (...args: unknown[]): T
}
export type MedusaContainer = AwilixContainer & {
registerAdd: <T>(name: string, registration: T) => MedusaContainer
}
export type MedusaContainer = coreMedusaContainer
export type Logger = _Logger & {
progress: (activityId: string, msg: string) => void
@@ -96,11 +91,7 @@ export type ConfigModule = {
admin_cors?: string
}
featureFlags: Record<string, boolean | string>
modules?: Record<
string,
false | string | Partial<ConfigurableModuleDeclaration>
>
moduleResolutions?: Record<string, ModuleResolution>
modules?: Record<string, false | string | Partial<InternalModuleDeclaration>>
plugins: (
| {
resolve: string
+2 -1
View File
@@ -1,6 +1,6 @@
import { Store, PaymentProvider, FulfillmentProvider } from "../models"
import { FeatureFlagsResponse } from "./feature-flags"
import { ModulesResponse } from "@medusajs/modules-sdk"
import { ModulesResponse as sdkModulesResponse } from "@medusajs/modules-sdk"
export type UpdateStoreInput = {
name?: string
@@ -29,6 +29,7 @@ export type UpdateStoreInput = {
* description: The resolution path of the module or false if module is not installed.
* type: string
*/
export type ModulesResponse = sdkModulesResponse
/**
* @schema ExtendedStoreDTO