Feat(modules-sdk,inventory,stock-location): modules isolated connection (#3329)
* feat: scoped container for modules
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 ?? [],
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user