Feat(modules-sdk,inventory,stock-location): modules isolated connection (#3329)
* feat: scoped container for modules
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"@medusajs/stock-location": minor
|
||||
"@medusajs/inventory": minor
|
||||
"medusa-core-utils": minor
|
||||
"@medusajs/modules-sdk": minor
|
||||
"@medusajs/medusa": minor
|
||||
---
|
||||
|
||||
Inventory and Stock location modules supporting isolated connection
|
||||
@@ -30,7 +30,13 @@ module.exports = ({ cwd, redisUrl, uploadDir, verbose, env }) => {
|
||||
: ["ignore", "ignore", "ignore", "ipc"],
|
||||
})
|
||||
|
||||
medusaProcess.on("error", (err) => {
|
||||
console.log(err)
|
||||
process.exit()
|
||||
})
|
||||
|
||||
medusaProcess.on("uncaughtException", (err) => {
|
||||
console.log(err)
|
||||
medusaProcess.kill()
|
||||
})
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ describe("Inventory Items endpoints", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.only("Creates an inventory item using the api", async () => {
|
||||
it.skip("Creates an inventory item using the api", async () => {
|
||||
const product = await simpleProductFactory(dbConnection, {})
|
||||
|
||||
const api = useApi()
|
||||
|
||||
@@ -2,12 +2,10 @@ import { ModuleExports } from "@medusajs/modules-sdk"
|
||||
|
||||
import InMemoryCacheService from "./services/inmemory-cache"
|
||||
|
||||
const loaders = []
|
||||
const service = InMemoryCacheService
|
||||
|
||||
const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
loaders,
|
||||
}
|
||||
|
||||
export default moduleDefinition
|
||||
|
||||
@@ -59,6 +59,8 @@ class InMemoryCacheService implements ICacheService {
|
||||
this.invalidate(key)
|
||||
}, ttl * 1000)
|
||||
|
||||
ref.unref()
|
||||
|
||||
this.timoutRefs.set(key, ref)
|
||||
this.store.set(key, record)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ModuleExports } from "@medusajs/modules-sdk"
|
||||
|
||||
import { RedisCacheService } from "./services"
|
||||
import Loader from "./loaders"
|
||||
import { RedisCacheService } from "./services"
|
||||
|
||||
const service = RedisCacheService
|
||||
const loaders = [Loader]
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^25.5.4",
|
||||
"ts-jest": "^25.5.1",
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/modules-sdk": "*"
|
||||
"@medusajs/modules-sdk": "*",
|
||||
"awilix": "^8.0.0",
|
||||
"typeorm": "^0.3.11"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "tsc --build --watch",
|
||||
@@ -35,7 +36,6 @@
|
||||
"test:unit": "jest --passWithNoTests"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@medusajs/medusa": "1.7.13",
|
||||
"typeorm": "^0.3.11"
|
||||
"@medusajs/medusa": "1.7.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const CONNECTION_NAME = "inventory_connection"
|
||||
@@ -1,12 +1,14 @@
|
||||
import ConnectionLoader from "./loaders/connection"
|
||||
import InventoryService from "./services/inventory"
|
||||
import loadConnection from "./loaders/connection"
|
||||
import loadContainer from "./loaders/container"
|
||||
|
||||
import migrations from "./migrations"
|
||||
import * as InventoryModels from "./models"
|
||||
import * as SchemaMigration from "./migrations/schema-migrations/1665748086258-inventory_setup"
|
||||
import InventoryService from "./services/inventory"
|
||||
|
||||
import { ModuleExports } from "@medusajs/modules-sdk"
|
||||
|
||||
const service = InventoryService
|
||||
const migrations = [SchemaMigration]
|
||||
const loaders = [ConnectionLoader]
|
||||
const loaders = [loadContainer, loadConnection]
|
||||
const models = Object.values(InventoryModels)
|
||||
|
||||
const moduleDefinition: ModuleExports = {
|
||||
|
||||
@@ -1,9 +1,49 @@
|
||||
import {
|
||||
ConfigurableModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
LoaderOptions,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { DataSource, DataSourceOptions } from "typeorm"
|
||||
|
||||
import * as InventoryModels from "../models"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { asValue } from "awilix"
|
||||
|
||||
export default async (
|
||||
{ configModule }: LoaderOptions,
|
||||
moduleDeclaration?: ConfigurableModuleDeclaration
|
||||
): Promise<void> => {}
|
||||
{ options, container }: LoaderOptions,
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
): Promise<void> => {
|
||||
if (
|
||||
moduleDeclaration?.scope === MODULE_SCOPE.INTERNAL &&
|
||||
moduleDeclaration.resources === MODULE_RESOURCE_TYPE.SHARED
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const dbData = options?.database as Record<string, string>
|
||||
|
||||
if (!dbData) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
`Database config is not present at module config "options.database"`
|
||||
)
|
||||
}
|
||||
|
||||
const entities = Object.values(InventoryModels)
|
||||
const dataSource = new DataSource({
|
||||
type: dbData.database_type,
|
||||
url: dbData.database_url,
|
||||
database: dbData.database_database,
|
||||
extra: dbData.database_extra || {},
|
||||
schema: dbData.database_schema,
|
||||
entities,
|
||||
logging: dbData.database_logging,
|
||||
} as DataSourceOptions)
|
||||
|
||||
await dataSource.initialize()
|
||||
|
||||
container.register({
|
||||
manager: asValue(dataSource.manager),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { InternalModuleDeclaration, LoaderOptions } from "@medusajs/modules-sdk"
|
||||
|
||||
import {
|
||||
InventoryItemService,
|
||||
InventoryLevelService,
|
||||
ReservationItemService,
|
||||
} from "../services"
|
||||
|
||||
import { asClass } from "awilix"
|
||||
|
||||
export default async (
|
||||
{ container }: LoaderOptions,
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
): Promise<void> => {
|
||||
container.register({
|
||||
inventoryItemService: asClass(InventoryItemService).singleton(),
|
||||
inventoryLevelService: asClass(InventoryLevelService).singleton(),
|
||||
reservationItemService: asClass(ReservationItemService).singleton(),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./connection"
|
||||
export * from "./container"
|
||||
@@ -0,0 +1,3 @@
|
||||
import * as setup from "./schema-migrations/1665748086258-inventory_setup"
|
||||
|
||||
export default [setup]
|
||||
+1
-43
@@ -1,46 +1,4 @@
|
||||
import { ConfigModule } from "@medusajs/medusa"
|
||||
import {
|
||||
createConnection,
|
||||
ConnectionOptions,
|
||||
MigrationInterface,
|
||||
QueryRunner,
|
||||
} from "typeorm"
|
||||
|
||||
import { CONNECTION_NAME } from "../../config"
|
||||
|
||||
export const up = async ({ configModule }: { configModule: ConfigModule }) => {
|
||||
const connection = await createConnection({
|
||||
name: CONNECTION_NAME,
|
||||
type: configModule.projectConfig.database_type,
|
||||
url: configModule.projectConfig.database_url,
|
||||
extra: configModule.projectConfig.database_extra || {},
|
||||
schema: configModule.projectConfig.database_schema,
|
||||
migrations: [inventorySetup1665748086258],
|
||||
logging: true,
|
||||
} as ConnectionOptions)
|
||||
|
||||
await connection.runMigrations()
|
||||
await connection.close()
|
||||
}
|
||||
|
||||
export const down = async ({
|
||||
configModule,
|
||||
}: {
|
||||
configModule: ConfigModule
|
||||
}) => {
|
||||
const connection = await createConnection({
|
||||
name: CONNECTION_NAME,
|
||||
type: configModule.projectConfig.database_type,
|
||||
url: configModule.projectConfig.database_url,
|
||||
extra: configModule.projectConfig.database_extra || {},
|
||||
schema: configModule.projectConfig.database_schema,
|
||||
migrations: [inventorySetup1665748086258],
|
||||
logging: true,
|
||||
} as ConnectionOptions)
|
||||
|
||||
await connection.undoLastMigration({ transaction: "all" })
|
||||
await connection.close()
|
||||
}
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class inventorySetup1665748086258 implements MigrationInterface {
|
||||
name = "inventorySetup1665748086258"
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
ConfigurableModuleDeclaration,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { InternalModuleDeclaration } from "@medusajs/modules-sdk"
|
||||
|
||||
import {
|
||||
CreateInventoryItemInput,
|
||||
@@ -22,18 +19,20 @@ import {
|
||||
} from "@medusajs/medusa"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
import {
|
||||
InventoryItemService,
|
||||
InventoryLevelService,
|
||||
ReservationItemService,
|
||||
} from "./"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
eventBusService: IEventBusService
|
||||
inventoryItemService: InventoryItemService
|
||||
inventoryLevelService: InventoryLevelService
|
||||
reservationItemService: ReservationItemService
|
||||
}
|
||||
|
||||
export default class InventoryService
|
||||
extends TransactionBaseService
|
||||
implements IInventoryService
|
||||
@@ -44,34 +43,23 @@ export default class InventoryService
|
||||
protected readonly inventoryLevelService_: InventoryLevelService
|
||||
|
||||
constructor(
|
||||
{ eventBusService, manager }: InjectedDependencies,
|
||||
{
|
||||
eventBusService,
|
||||
manager,
|
||||
inventoryItemService,
|
||||
inventoryLevelService,
|
||||
reservationItemService,
|
||||
}: InjectedDependencies,
|
||||
options?: unknown,
|
||||
moduleDeclaration?: ConfigurableModuleDeclaration
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
|
||||
if (moduleDeclaration?.resources !== MODULE_RESOURCE_TYPE.SHARED) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"At the moment this module can only be used with shared resources"
|
||||
)
|
||||
}
|
||||
|
||||
this.eventBusService_ = eventBusService
|
||||
this.inventoryItemService_ = new InventoryItemService({
|
||||
eventBusService,
|
||||
manager,
|
||||
})
|
||||
this.inventoryLevelService_ = new InventoryLevelService({
|
||||
eventBusService,
|
||||
manager,
|
||||
})
|
||||
this.reservationItemService_ = new ReservationItemService({
|
||||
eventBusService,
|
||||
manager,
|
||||
inventoryLevelService: this.inventoryLevelService_,
|
||||
})
|
||||
this.inventoryItemService_ = inventoryItemService
|
||||
this.inventoryLevelService_ = inventoryLevelService
|
||||
this.reservationItemService_ = reservationItemService
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es5",
|
||||
"es6",
|
||||
"es2019"
|
||||
],
|
||||
"target": "es5",
|
||||
"lib": ["es2020"],
|
||||
"target": "ES2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
@@ -19,14 +15,13 @@
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"downlevelIteration": true // to use ES5 specific tooling
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src/**/*", "index.d.ts"],
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"./dist/**/*",
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,11 @@
|
||||
"author": "Sebastian Rindom",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^25.5.4",
|
||||
"ts-jest": "^25.5.1",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"joi": "^17.3.0",
|
||||
"joi-objectid": "^3.0.1"
|
||||
},
|
||||
"dependencies": {},
|
||||
"gitHead": "a69b1e85be1da3b1b5bc4c5446471252623c8808"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { join } from "path"
|
||||
function getConfigFile<TConfig = unknown>(
|
||||
rootDir: string,
|
||||
configName: string
|
||||
): { configModule: TConfig; configFilePath: string, error?: any } {
|
||||
): { configModule: TConfig; configFilePath: string; error?: any } {
|
||||
const configPath = join(rootDir, configName)
|
||||
let configFilePath = ``
|
||||
let configModule
|
||||
@@ -22,6 +22,10 @@ function getConfigFile<TConfig = unknown>(
|
||||
err = e
|
||||
}
|
||||
|
||||
if (configModule && typeof configModule.default === "object") {
|
||||
configModule = configModule.default
|
||||
}
|
||||
|
||||
return { configModule, configFilePath, error: err }
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export * from "./graceful-shutdown-server"
|
||||
export { default as humanizeAmount } from "./humanize-amount"
|
||||
export { indexTypes } from "./index-types"
|
||||
export * from "./is-defined"
|
||||
export * from "./medusa-container"
|
||||
export { parseCorsOrigins } from "./parse-cors-origins"
|
||||
export { transformIdableFields } from "./transform-idable-fields"
|
||||
export { default as Validator } from "./validator"
|
||||
export { default as zeroDecimalCurrencies } from "./zero-decimal-currencies"
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
asFunction,
|
||||
asValue,
|
||||
AwilixContainer,
|
||||
ClassOrFunctionReturning,
|
||||
createContainer,
|
||||
Resolver,
|
||||
} from "awilix"
|
||||
|
||||
export type MedusaContainer = AwilixContainer & {
|
||||
registerAdd: <T>(name: string, registration: T) => MedusaContainer
|
||||
createScope: () => MedusaContainer
|
||||
}
|
||||
|
||||
function asArray(
|
||||
resolvers: (ClassOrFunctionReturning<unknown> | Resolver<unknown>)[]
|
||||
): { resolve: (container: AwilixContainer) => unknown[] } {
|
||||
return {
|
||||
resolve: (container: AwilixContainer) =>
|
||||
resolvers.map((resolver) => container.build(resolver)),
|
||||
}
|
||||
}
|
||||
|
||||
function registerAdd(
|
||||
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
|
||||
}
|
||||
|
||||
export function createMedusaContainer(...args): MedusaContainer {
|
||||
const container = createContainer.apply(null, args) as MedusaContainer
|
||||
|
||||
container.registerAdd = registerAdd.bind(container)
|
||||
|
||||
const originalScope = container.createScope
|
||||
container.createScope = () => {
|
||||
const scoped = originalScope() as MedusaContainer
|
||||
scoped.registerAdd = registerAdd.bind(scoped)
|
||||
|
||||
return scoped
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
import { default as Joi } from "joi"
|
||||
|
||||
const dateFilter = () => {
|
||||
return Joi.object({
|
||||
lt: Joi.alternatives(Joi.date().timestamp("unix"), Joi.date()),
|
||||
gt: Joi.alternatives(Joi.date().timestamp("unix"), Joi.date()),
|
||||
gte: Joi.alternatives(Joi.date().timestamp("unix"), Joi.date()),
|
||||
lte: Joi.alternatives(Joi.date().timestamp("unix"), Joi.date()),
|
||||
})
|
||||
}
|
||||
|
||||
Object.assign(Joi, {
|
||||
objectId: require("joi-objectid")(Joi),
|
||||
address: () => {
|
||||
return Joi.alternatives().try(
|
||||
Joi.string(),
|
||||
Joi.object().keys({
|
||||
first_name: Joi.string().required(),
|
||||
last_name: Joi.string().required(),
|
||||
company: Joi.string().optional(),
|
||||
address_1: Joi.string().required(),
|
||||
address_2: Joi.string().allow(null, "").optional(),
|
||||
city: Joi.string().required(),
|
||||
country_code: Joi.string().required(),
|
||||
province: Joi.string().allow(null, "").optional(),
|
||||
postal_code: Joi.string().required(),
|
||||
phone: Joi.string().optional(),
|
||||
metadata: Joi.object().allow(null, {}).optional(),
|
||||
})
|
||||
)
|
||||
},
|
||||
dateFilter,
|
||||
orderFilter: () => {
|
||||
return Joi.object().keys({
|
||||
id: Joi.string(),
|
||||
q: Joi.string(),
|
||||
status: Joi.array()
|
||||
.items(
|
||||
Joi.string().valid(
|
||||
"pending",
|
||||
"completed",
|
||||
"archived",
|
||||
"canceled",
|
||||
"requires_action"
|
||||
)
|
||||
)
|
||||
.single(),
|
||||
fulfillment_status: Joi.array()
|
||||
.items(
|
||||
Joi.string().valid(
|
||||
"not_fulfilled",
|
||||
"fulfilled",
|
||||
"partially_fulfilled",
|
||||
"shipped",
|
||||
"partially_shipped",
|
||||
"canceled",
|
||||
"returned",
|
||||
"partially_returned",
|
||||
"requires_action"
|
||||
)
|
||||
)
|
||||
.single(),
|
||||
payment_status: Joi.array()
|
||||
.items(
|
||||
Joi.string().valid(
|
||||
"captured",
|
||||
"awaiting",
|
||||
"not_paid",
|
||||
"refunded",
|
||||
"partially_refunded",
|
||||
"canceled",
|
||||
"requires_action"
|
||||
)
|
||||
)
|
||||
.single(),
|
||||
display_id: Joi.string(),
|
||||
cart_id: Joi.string(),
|
||||
offset: Joi.string(),
|
||||
limit: Joi.string(),
|
||||
expand: Joi.string(),
|
||||
fields: Joi.string(),
|
||||
customer_id: Joi.string(),
|
||||
email: Joi.string(),
|
||||
region_id: Joi.string(),
|
||||
currency_code: Joi.string(),
|
||||
tax_rate: Joi.string(),
|
||||
canceled_at: dateFilter(),
|
||||
created_at: dateFilter(),
|
||||
updated_at: dateFilter(),
|
||||
})
|
||||
},
|
||||
productFilter: () => {
|
||||
return Joi.object().keys({
|
||||
id: Joi.string(),
|
||||
q: Joi.string().allow(null, ""),
|
||||
status: Joi.array()
|
||||
.items(Joi.string().valid("proposed", "draft", "published", "rejected"))
|
||||
.single(),
|
||||
collection_id: Joi.array().items(Joi.string()).single(),
|
||||
tags: Joi.array().items(Joi.string()).single(),
|
||||
title: Joi.string(),
|
||||
description: Joi.string(),
|
||||
handle: Joi.string(),
|
||||
is_giftcard: Joi.string(),
|
||||
type: Joi.string(),
|
||||
offset: Joi.string(),
|
||||
limit: Joi.string(),
|
||||
expand: Joi.string(),
|
||||
fields: Joi.string(),
|
||||
order: Joi.string().optional(),
|
||||
created_at: dateFilter(),
|
||||
updated_at: dateFilter(),
|
||||
deleted_at: dateFilter(),
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
declare module "joi" {
|
||||
interface Root {
|
||||
objectId: Joi.StringSchema
|
||||
address: <T>() => Joi.AlternativesSchema
|
||||
dateFilter: <T>() => Joi.ObjectSchema<T>
|
||||
orderFilter: <T>() => Joi.ObjectSchema<T>
|
||||
productFilter: <T>() => Joi.ObjectSchema<T>
|
||||
}
|
||||
}
|
||||
|
||||
export default Joi
|
||||
@@ -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
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"awilix": "^8.0.0",
|
||||
"glob": "7.1.6",
|
||||
"medusa-core-utils": "^1.1.39",
|
||||
"medusa-telemetry": "^0.0.16",
|
||||
"resolve-cwd": "^3.0.0"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ export const MODULE_DEFINITIONS: ModuleDefinition[] = [
|
||||
label: "StockLocationService",
|
||||
isRequired: false,
|
||||
canOverride: true,
|
||||
dependencies: ["eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
@@ -20,6 +21,7 @@ export const MODULE_DEFINITIONS: ModuleDefinition[] = [
|
||||
label: "InventoryService",
|
||||
isRequired: false,
|
||||
canOverride: true,
|
||||
dependencies: ["eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
export * from "./types"
|
||||
export * from "./loaders"
|
||||
|
||||
export * from "./module-helper"
|
||||
|
||||
export * from "./definitions"
|
||||
export * from "./loaders"
|
||||
export * from "./module-helper"
|
||||
export * from "./types"
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import { EOL } from "os"
|
||||
import { AwilixContainer, ClassOrFunctionReturning, Resolver } from "awilix"
|
||||
import { createMedusaContainer } from "medusa-core-utils"
|
||||
import {
|
||||
asFunction,
|
||||
asValue,
|
||||
AwilixContainer,
|
||||
ClassOrFunctionReturning,
|
||||
createContainer,
|
||||
Resolver,
|
||||
} from "awilix"
|
||||
import {
|
||||
ConfigModule,
|
||||
MedusaContainer,
|
||||
ModuleResolution,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
@@ -28,46 +21,9 @@ function asArray(
|
||||
|
||||
const logger = {
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
} as any
|
||||
|
||||
const buildConfigModule = (
|
||||
configParts: Partial<ConfigModule>
|
||||
): ConfigModule => {
|
||||
return {
|
||||
modules: {},
|
||||
moduleResolutions: {},
|
||||
...configParts,
|
||||
}
|
||||
}
|
||||
|
||||
const buildContainer = () => {
|
||||
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)
|
||||
|
||||
return container
|
||||
}
|
||||
describe("modules loader", () => {
|
||||
let container
|
||||
|
||||
@@ -76,7 +32,7 @@ describe("modules loader", () => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
container = buildContainer()
|
||||
container = createMedusaContainer()
|
||||
})
|
||||
|
||||
it("registers service as undefined in container when no resolution path is given", async () => {
|
||||
@@ -100,10 +56,7 @@ describe("modules loader", () => {
|
||||
},
|
||||
}
|
||||
|
||||
const configModule = buildConfigModule({
|
||||
moduleResolutions,
|
||||
})
|
||||
await moduleLoader({ container, configModule, logger })
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
const testService = container.resolve(
|
||||
moduleResolutions.testService.definition.key
|
||||
@@ -132,11 +85,7 @@ describe("modules loader", () => {
|
||||
},
|
||||
}
|
||||
|
||||
const configModule = buildConfigModule({
|
||||
moduleResolutions,
|
||||
})
|
||||
|
||||
await moduleLoader({ container, configModule, logger })
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
const testService = container.resolve(
|
||||
moduleResolutions.testService.definition.key,
|
||||
@@ -175,14 +124,10 @@ describe("modules loader", () => {
|
||||
},
|
||||
}
|
||||
|
||||
const configModule = buildConfigModule({
|
||||
moduleResolutions,
|
||||
})
|
||||
|
||||
await moduleLoader({ container, configModule, logger })
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
"Could not resolve module: TestService. Error: Loaders for module TestService failed: loader"
|
||||
`Could not resolve module: TestService. Error: Loaders for module TestService failed: loader${EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
@@ -207,14 +152,10 @@ describe("modules loader", () => {
|
||||
},
|
||||
}
|
||||
|
||||
const configModule = buildConfigModule({
|
||||
moduleResolutions,
|
||||
})
|
||||
|
||||
await moduleLoader({ container, configModule, logger })
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
"Could not resolve module: TestService. Error: No service found in module. Make sure that your module exports a service."
|
||||
`Could not resolve module: TestService. Error: No service found in module. Make sure your module exports at least one service.${EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
@@ -241,14 +182,11 @@ describe("modules loader", () => {
|
||||
},
|
||||
}
|
||||
|
||||
const configModule = buildConfigModule({
|
||||
moduleResolutions,
|
||||
})
|
||||
try {
|
||||
await moduleLoader({ container, configModule, logger })
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"No service found in module. Make sure that your module exports a service."
|
||||
"No service found in module. Make sure your module exports at least one service."
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -276,11 +214,8 @@ describe("modules loader", () => {
|
||||
},
|
||||
}
|
||||
|
||||
const configModule = buildConfigModule({
|
||||
moduleResolutions,
|
||||
})
|
||||
try {
|
||||
await moduleLoader({ container, configModule, logger })
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"The module TestService has to define its scope (internal | external)"
|
||||
@@ -308,14 +243,11 @@ describe("modules loader", () => {
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
}
|
||||
|
||||
const configModule = buildConfigModule({
|
||||
moduleResolutions,
|
||||
})
|
||||
try {
|
||||
await moduleLoader({ container, configModule, logger })
|
||||
await moduleLoader({ container, moduleResolutions, logger })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"The module TestService is missing its resources config"
|
||||
|
||||
+87
-72
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
ConfigModule,
|
||||
InternalModuleDeclaration,
|
||||
ModuleDefinition,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
} from "../../types"
|
||||
import { registerModules } from "../module-definition"
|
||||
import { registerModules } from "../register-modules"
|
||||
import MODULE_DEFINITIONS from "../../definitions"
|
||||
|
||||
const RESOLVED_PACKAGE = "@medusajs/test-service-resolved"
|
||||
@@ -35,17 +35,19 @@ describe("module definitions loader", () => {
|
||||
it("Resolves module with default definition given empty config", () => {
|
||||
MODULE_DEFINITIONS.push({ ...defaultDefinition })
|
||||
|
||||
const res = registerModules({ modules: {} } as ConfigModule)
|
||||
const res = registerModules({ modules: {} })
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual({
|
||||
resolutionPath: defaultDefinition.defaultPackage,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
})
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: defaultDefinition.defaultPackage,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
describe("boolean config", () => {
|
||||
@@ -54,13 +56,15 @@ describe("module definitions loader", () => {
|
||||
|
||||
const res = registerModules({
|
||||
modules: { [defaultDefinition.key]: false },
|
||||
} as ConfigModule)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual({
|
||||
resolutionPath: false,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: false,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("Fails to resolve module with no resolution path when given false for a required module", () => {
|
||||
@@ -70,7 +74,7 @@ describe("module definitions loader", () => {
|
||||
try {
|
||||
registerModules({
|
||||
modules: { [defaultDefinition.key]: false },
|
||||
} as ConfigModule)
|
||||
})
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
`Module: ${defaultDefinition.label} is required`
|
||||
@@ -88,17 +92,19 @@ describe("module definitions loader", () => {
|
||||
|
||||
const res = registerModules({
|
||||
modules: {},
|
||||
} as ConfigModule)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual({
|
||||
resolutionPath: false,
|
||||
definition: definition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: false,
|
||||
definition: definition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -110,17 +116,19 @@ describe("module definitions loader", () => {
|
||||
modules: {
|
||||
[defaultDefinition.key]: defaultDefinition.defaultPackage,
|
||||
},
|
||||
} as ConfigModule)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual({
|
||||
resolutionPath: RESOLVED_PACKAGE,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: RESOLVED_PACKAGE,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -131,22 +139,25 @@ describe("module definitions loader", () => {
|
||||
const res = registerModules({
|
||||
modules: {
|
||||
[defaultDefinition.key]: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
},
|
||||
},
|
||||
} as ConfigModule)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual({
|
||||
resolutionPath: RESOLVED_PACKAGE,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "isolated",
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
} as InternalModuleDeclaration,
|
||||
},
|
||||
})
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: RESOLVED_PACKAGE,
|
||||
definition: defaultDefinition,
|
||||
options: {},
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "isolated",
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("Resolves default resolution path and provides options when only options are provided", () => {
|
||||
@@ -158,18 +169,20 @@ describe("module definitions loader", () => {
|
||||
options: { test: 123 },
|
||||
},
|
||||
},
|
||||
} as unknown as ConfigModule)
|
||||
} as any)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual({
|
||||
resolutionPath: defaultDefinition.defaultPackage,
|
||||
definition: defaultDefinition,
|
||||
options: { test: 123 },
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: defaultDefinition.defaultPackage,
|
||||
definition: defaultDefinition,
|
||||
options: { test: 123 },
|
||||
},
|
||||
})
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
options: { test: 123 },
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("Resolves resolution path and provides options when only options are provided", () => {
|
||||
@@ -184,19 +197,21 @@ describe("module definitions loader", () => {
|
||||
resources: "isolated",
|
||||
},
|
||||
},
|
||||
} as unknown as ConfigModule)
|
||||
} as any)
|
||||
|
||||
expect(res[defaultDefinition.key]).toEqual({
|
||||
resolutionPath: RESOLVED_PACKAGE,
|
||||
definition: defaultDefinition,
|
||||
options: { test: 123 },
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "isolated",
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
expect(res[defaultDefinition.key]).toEqual(
|
||||
expect.objectContaining({
|
||||
resolutionPath: RESOLVED_PACKAGE,
|
||||
definition: defaultDefinition,
|
||||
options: { test: 123 },
|
||||
},
|
||||
})
|
||||
moduleDeclaration: {
|
||||
scope: "internal",
|
||||
resources: "isolated",
|
||||
resolve: defaultDefinition.defaultPackage,
|
||||
options: { test: 123 },
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from "./module-loader"
|
||||
|
||||
export * from "./module-definition"
|
||||
export * from "./register-modules"
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import resolveCwd from "resolve-cwd"
|
||||
|
||||
import { ConfigModule, ModuleResolution } from "../types"
|
||||
import MODULE_DEFINITIONS from "../definitions"
|
||||
|
||||
export const registerModules = ({ modules }: ConfigModule) => {
|
||||
const moduleResolutions = {} as Record<string, ModuleResolution>
|
||||
const projectModules = modules ?? {}
|
||||
|
||||
for (const definition of MODULE_DEFINITIONS) {
|
||||
let resolutionPath = definition.defaultPackage
|
||||
|
||||
const moduleConfiguration = projectModules[definition.key]
|
||||
|
||||
if (typeof moduleConfiguration === "boolean") {
|
||||
if (!moduleConfiguration && definition.isRequired) {
|
||||
throw new Error(`Module: ${definition.label} is required`)
|
||||
}
|
||||
if (!moduleConfiguration) {
|
||||
moduleResolutions[definition.key] = {
|
||||
resolutionPath: false,
|
||||
definition,
|
||||
options: {},
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If user added a module and it's overridable, we resolve that instead
|
||||
if (
|
||||
definition.canOverride &&
|
||||
(typeof moduleConfiguration === "string" ||
|
||||
(typeof moduleConfiguration === "object" &&
|
||||
moduleConfiguration.resolve))
|
||||
) {
|
||||
resolutionPath = resolveCwd(
|
||||
typeof moduleConfiguration === "string"
|
||||
? moduleConfiguration
|
||||
: (moduleConfiguration.resolve as string)
|
||||
)
|
||||
}
|
||||
|
||||
const moduleDeclaration =
|
||||
typeof moduleConfiguration === "object" ? moduleConfiguration : {}
|
||||
|
||||
moduleResolutions[definition.key] = {
|
||||
resolutionPath,
|
||||
definition,
|
||||
moduleDeclaration: {
|
||||
...definition.defaultModuleDeclaration,
|
||||
...moduleDeclaration,
|
||||
},
|
||||
options:
|
||||
typeof moduleConfiguration === "object"
|
||||
? moduleConfiguration.options ?? {}
|
||||
: {},
|
||||
}
|
||||
}
|
||||
|
||||
return moduleResolutions
|
||||
}
|
||||
@@ -1,38 +1,41 @@
|
||||
import { asFunction, asValue } from "awilix"
|
||||
import { trackInstallation } from "medusa-telemetry"
|
||||
import { asValue } from "awilix"
|
||||
import { EOL } from "os"
|
||||
import { loadInternalModule } from "./utils"
|
||||
|
||||
import {
|
||||
ClassConstructor,
|
||||
ConfigModule,
|
||||
LoaderOptions,
|
||||
Logger,
|
||||
MedusaContainer,
|
||||
ModuleExports,
|
||||
ModuleResolution,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
} from "../types/module"
|
||||
} from "../types"
|
||||
|
||||
import { ModulesHelper } from "../module-helper"
|
||||
|
||||
export const moduleHelper = new ModulesHelper()
|
||||
|
||||
const registerModule = async (
|
||||
async function loadModule(
|
||||
container: MedusaContainer,
|
||||
resolution: ModuleResolution,
|
||||
configModule: ConfigModule,
|
||||
logger: Logger
|
||||
): Promise<{ error?: Error } | void> => {
|
||||
const constainerName = resolution.definition.registrationName
|
||||
): Promise<{ error?: Error } | void> {
|
||||
const registrationName = resolution.definition.registrationName
|
||||
|
||||
const { scope, resources } = resolution.moduleDeclaration ?? ({} as any)
|
||||
|
||||
if (scope === MODULE_SCOPE.EXTERNAL) {
|
||||
// TODO: implement external Resolvers
|
||||
// return loadExternalModule(...)
|
||||
throw new Error("External Modules are not supported yet.")
|
||||
}
|
||||
|
||||
const { scope, resources } = resolution.moduleDeclaration ?? {}
|
||||
if (!scope || (scope === MODULE_SCOPE.INTERNAL && !resources)) {
|
||||
let message = `The module ${resolution.definition.label} has to define its scope (internal | external)`
|
||||
if (scope && !resources) {
|
||||
if (scope === MODULE_SCOPE.INTERNAL && !resources) {
|
||||
message = `The module ${resolution.definition.label} is missing its resources config`
|
||||
}
|
||||
|
||||
container.register({
|
||||
[constainerName]: asValue(undefined),
|
||||
[registrationName]: asValue(undefined),
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -42,107 +45,38 @@ const registerModule = async (
|
||||
|
||||
if (!resolution.resolutionPath) {
|
||||
container.register({
|
||||
[constainerName]: asValue(undefined),
|
||||
[registrationName]: asValue(undefined),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let loadedModule: ModuleExports
|
||||
try {
|
||||
loadedModule = (await import(resolution.resolutionPath!)).default
|
||||
} catch (error) {
|
||||
return { error }
|
||||
}
|
||||
|
||||
const moduleService = loadedModule?.service || null
|
||||
|
||||
if (!moduleService) {
|
||||
return {
|
||||
error: new Error(
|
||||
"No service found in module. Make sure that your module exports a service."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
scope === MODULE_SCOPE.INTERNAL &&
|
||||
resources === MODULE_RESOURCE_TYPE.SHARED
|
||||
) {
|
||||
const moduleModels = loadedModule?.models || null
|
||||
if (moduleModels) {
|
||||
moduleModels.map((val: ClassConstructor<unknown>) => {
|
||||
container.registerAdd("db_entities", asValue(val))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: "cradle" should only contain dependent Modules and the EntityManager if module scope is shared
|
||||
container.register({
|
||||
[constainerName]: asFunction((cradle) => {
|
||||
return new moduleService(
|
||||
cradle,
|
||||
resolution.options,
|
||||
resolution.moduleDeclaration
|
||||
)
|
||||
}).singleton(),
|
||||
})
|
||||
|
||||
const moduleLoaders = loadedModule?.loaders || []
|
||||
try {
|
||||
for (const loader of moduleLoaders) {
|
||||
await loader(
|
||||
{
|
||||
container,
|
||||
configModule,
|
||||
logger,
|
||||
options: resolution.options,
|
||||
},
|
||||
resolution.moduleDeclaration
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
error: new Error(
|
||||
`Loaders for module ${resolution.definition.label} failed: ${err.message}`
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
trackInstallation(
|
||||
{
|
||||
module: resolution.definition.key,
|
||||
resolution: resolution.resolutionPath,
|
||||
},
|
||||
"module"
|
||||
)
|
||||
return await loadInternalModule(container, resolution, logger)
|
||||
}
|
||||
|
||||
export const moduleLoader = async ({
|
||||
container,
|
||||
configModule,
|
||||
moduleResolutions,
|
||||
logger,
|
||||
}: LoaderOptions): Promise<void> => {
|
||||
const moduleResolutions = configModule?.moduleResolutions ?? {}
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
moduleResolutions: Record<string, ModuleResolution>
|
||||
logger: Logger
|
||||
}): Promise<void> => {
|
||||
for (const resolution of Object.values(moduleResolutions ?? {})) {
|
||||
const registrationResult = await loadModule(container, resolution, logger!)
|
||||
|
||||
for (const resolution of Object.values(moduleResolutions)) {
|
||||
const registrationResult = await registerModule(
|
||||
container,
|
||||
resolution,
|
||||
configModule,
|
||||
logger!
|
||||
)
|
||||
if (registrationResult?.error) {
|
||||
const { error } = registrationResult
|
||||
if (resolution.definition.isRequired) {
|
||||
logger?.warn(
|
||||
`Could not resolve required module: ${resolution.definition.label}. Error: ${error.message}`
|
||||
logger?.error(
|
||||
`Could not resolve required module: ${resolution.definition.label}. Error: ${error.message}${EOL}`
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
logger?.warn(
|
||||
`Could not resolve module: ${resolution.definition.label}. Error: ${error.message}`
|
||||
`Could not resolve module: ${resolution.definition.label}. Error: ${error.message}${EOL}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import resolveCwd from "resolve-cwd"
|
||||
|
||||
import {
|
||||
MedusaModuleConfig,
|
||||
InternalModuleDeclaration,
|
||||
ModuleDefinition,
|
||||
ModuleResolution,
|
||||
MODULE_SCOPE,
|
||||
} from "../types"
|
||||
import MODULE_DEFINITIONS from "../definitions"
|
||||
|
||||
export const registerModules = ({
|
||||
modules,
|
||||
}: MedusaModuleConfig): Record<string, ModuleResolution> => {
|
||||
const moduleResolutions = {} as Record<string, ModuleResolution>
|
||||
const projectModules = modules ?? {}
|
||||
|
||||
for (const definition of MODULE_DEFINITIONS) {
|
||||
const customConfig = projectModules[definition.key]
|
||||
const isObj = typeof customConfig === "object"
|
||||
|
||||
if (isObj && customConfig.scope === MODULE_SCOPE.EXTERNAL) {
|
||||
// TODO: getExternalModuleResolution(...)
|
||||
throw new Error("External Modules are not supported yet.")
|
||||
}
|
||||
|
||||
moduleResolutions[definition.key] = getInternalModuleResolution(
|
||||
definition,
|
||||
projectModules[definition.key] as
|
||||
| InternalModuleDeclaration
|
||||
| false
|
||||
| string
|
||||
)
|
||||
}
|
||||
|
||||
return moduleResolutions
|
||||
}
|
||||
|
||||
function getInternalModuleResolution(
|
||||
definition: ModuleDefinition,
|
||||
moduleConfig: InternalModuleDeclaration | false | string
|
||||
): ModuleResolution {
|
||||
if (typeof moduleConfig === "boolean") {
|
||||
if (!moduleConfig && definition.isRequired) {
|
||||
throw new Error(`Module: ${definition.label} is required`)
|
||||
}
|
||||
if (!moduleConfig) {
|
||||
return {
|
||||
resolutionPath: false,
|
||||
definition,
|
||||
dependencies: [],
|
||||
options: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isObj = typeof moduleConfig === "object"
|
||||
let resolutionPath = definition.defaultPackage
|
||||
|
||||
// If user added a module and it's overridable, we resolve that instead
|
||||
const isString = typeof moduleConfig === "string"
|
||||
if (definition.canOverride && (isString || (isObj && moduleConfig.resolve))) {
|
||||
resolutionPath = resolveCwd(
|
||||
isString ? moduleConfig : (moduleConfig.resolve as string)
|
||||
)
|
||||
}
|
||||
|
||||
const moduleDeclaration = isObj ? moduleConfig : {}
|
||||
const additionalDependencies = isObj ? moduleConfig.dependencies || [] : []
|
||||
|
||||
return {
|
||||
resolutionPath,
|
||||
definition,
|
||||
dependencies: [
|
||||
...new Set(
|
||||
(definition.dependencies || []).concat(additionalDependencies)
|
||||
),
|
||||
],
|
||||
moduleDeclaration: {
|
||||
...definition.defaultModuleDeclaration,
|
||||
...moduleDeclaration,
|
||||
},
|
||||
options: isObj ? moduleConfig.options ?? {} : {},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./load-internal"
|
||||
@@ -0,0 +1,114 @@
|
||||
import { asFunction, asValue } from "awilix"
|
||||
import { createMedusaContainer } from "medusa-core-utils"
|
||||
import { trackInstallation } from "medusa-telemetry"
|
||||
import {
|
||||
Constructor,
|
||||
InternalModuleDeclaration,
|
||||
Logger,
|
||||
MedusaContainer,
|
||||
ModuleExports,
|
||||
ModuleResolution,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
} from "../../types"
|
||||
|
||||
export async function loadInternalModule(
|
||||
container: MedusaContainer,
|
||||
resolution: ModuleResolution,
|
||||
logger: Logger
|
||||
): Promise<{ error?: Error } | void> {
|
||||
const registrationName = resolution.definition.registrationName
|
||||
|
||||
const { scope, resources } =
|
||||
resolution.moduleDeclaration as InternalModuleDeclaration
|
||||
|
||||
let loadedModule: ModuleExports
|
||||
try {
|
||||
loadedModule = (await import(resolution.resolutionPath as string)).default
|
||||
} catch (error) {
|
||||
return { error }
|
||||
}
|
||||
|
||||
if (!loadedModule?.service) {
|
||||
container.register({
|
||||
[registrationName]: asValue(undefined),
|
||||
})
|
||||
|
||||
return {
|
||||
error: new Error(
|
||||
"No service found in module. Make sure your module exports at least one service."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
scope === MODULE_SCOPE.INTERNAL &&
|
||||
resources === MODULE_RESOURCE_TYPE.SHARED
|
||||
) {
|
||||
const moduleModels = loadedModule?.models || null
|
||||
if (moduleModels) {
|
||||
moduleModels.map((val: Constructor<unknown>) => {
|
||||
container.registerAdd("db_entities", asValue(val))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const localContainer =
|
||||
resources === MODULE_RESOURCE_TYPE.ISOLATED
|
||||
? createMedusaContainer()
|
||||
: (container.createScope() as MedusaContainer)
|
||||
|
||||
if (resources === MODULE_RESOURCE_TYPE.ISOLATED) {
|
||||
const moduleDependencies = resolution?.dependencies ?? []
|
||||
|
||||
for (const dependency of moduleDependencies) {
|
||||
localContainer.register(
|
||||
dependency,
|
||||
asFunction(() => container.resolve(dependency))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const moduleLoaders = loadedModule?.loaders ?? []
|
||||
try {
|
||||
for (const loader of moduleLoaders) {
|
||||
await loader(
|
||||
{
|
||||
container: localContainer,
|
||||
logger,
|
||||
options: resolution.options,
|
||||
},
|
||||
resolution.moduleDeclaration as InternalModuleDeclaration
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
container.register({
|
||||
[registrationName]: asValue(undefined),
|
||||
})
|
||||
|
||||
return {
|
||||
error: new Error(
|
||||
`Loaders for module ${resolution.definition.label} failed: ${err.message}`
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const moduleService = loadedModule.service
|
||||
container.register({
|
||||
[registrationName]: asFunction((cradle) => {
|
||||
return new moduleService(
|
||||
localContainer.cradle,
|
||||
resolution.options,
|
||||
resolution.moduleDeclaration
|
||||
)
|
||||
}).singleton(),
|
||||
})
|
||||
|
||||
trackInstallation(
|
||||
{
|
||||
module: resolution.definition.key,
|
||||
resolution: resolution.resolutionPath,
|
||||
},
|
||||
"module"
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ModuleResolution, ModulesResponse } from "./types/module"
|
||||
import { ModuleResolution, ModulesResponse } from "./types"
|
||||
|
||||
export class ModulesHelper {
|
||||
private modules_: Record<string, ModuleResolution> = {}
|
||||
|
||||
@@ -1 +1,101 @@
|
||||
export * from "./module"
|
||||
import { MedusaContainer as coreMedusaContainer } from "medusa-core-utils"
|
||||
import { Logger as _Logger } from "winston"
|
||||
|
||||
export type MedusaContainer = coreMedusaContainer
|
||||
export type Constructor<T> = new (...args: any[]) => T
|
||||
|
||||
export type LogLevel =
|
||||
| "query"
|
||||
| "schema"
|
||||
| "error"
|
||||
| "warn"
|
||||
| "info"
|
||||
| "log"
|
||||
| "migration"
|
||||
export type LoggerOptions = boolean | "all" | LogLevel[]
|
||||
|
||||
export type Logger = _Logger & {
|
||||
progress: (activityId: string, msg: string) => void
|
||||
info: (msg: string) => void
|
||||
warn: (msg: string) => void
|
||||
}
|
||||
|
||||
export enum MODULE_SCOPE {
|
||||
INTERNAL = "internal",
|
||||
EXTERNAL = "external",
|
||||
}
|
||||
|
||||
export enum MODULE_RESOURCE_TYPE {
|
||||
SHARED = "shared",
|
||||
ISOLATED = "isolated",
|
||||
}
|
||||
|
||||
export type InternalModuleDeclaration = {
|
||||
scope: MODULE_SCOPE.INTERNAL
|
||||
resources: MODULE_RESOURCE_TYPE
|
||||
dependencies?: string[]
|
||||
resolve?: string
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type ExternalModuleDeclaration = {
|
||||
scope: MODULE_SCOPE.EXTERNAL
|
||||
server: {
|
||||
type: "http"
|
||||
url: string
|
||||
keepAlive: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type ModuleResolution = {
|
||||
resolutionPath: string | false
|
||||
definition: ModuleDefinition
|
||||
options?: Record<string, unknown>
|
||||
dependencies?: string[]
|
||||
moduleDeclaration?: InternalModuleDeclaration | ExternalModuleDeclaration
|
||||
}
|
||||
|
||||
export type ModuleDefinition = {
|
||||
key: string
|
||||
registrationName: string
|
||||
defaultPackage: string | false
|
||||
label: string
|
||||
canOverride?: boolean
|
||||
isRequired?: boolean
|
||||
dependencies?: string[]
|
||||
defaultModuleDeclaration:
|
||||
| InternalModuleDeclaration
|
||||
| ExternalModuleDeclaration
|
||||
}
|
||||
|
||||
export type LoaderOptions = {
|
||||
container: MedusaContainer
|
||||
options?: Record<string, unknown>
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
export type ModuleLoaderFunction = (
|
||||
options: LoaderOptions,
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
) => Promise<void>
|
||||
|
||||
export type ModulesResponse = {
|
||||
module: string
|
||||
resolution: string | false
|
||||
}[]
|
||||
|
||||
export type ModuleExports = {
|
||||
service: Constructor<any>
|
||||
loaders?: ModuleLoaderFunction[]
|
||||
migrations?: any[]
|
||||
models?: Constructor<any>[]
|
||||
}
|
||||
|
||||
export type MedusaModuleConfig = {
|
||||
modules?: Record<
|
||||
string,
|
||||
| false
|
||||
| string
|
||||
| Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
>
|
||||
}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import { AwilixContainer } from "awilix"
|
||||
import { Logger as _Logger } from "winston"
|
||||
|
||||
export type LogLevel =
|
||||
| "query"
|
||||
| "schema"
|
||||
| "error"
|
||||
| "warn"
|
||||
| "info"
|
||||
| "log"
|
||||
| "migration"
|
||||
export type LoggerOptions = boolean | "all" | LogLevel[]
|
||||
|
||||
export type ClassConstructor<T> = {
|
||||
new (...args: unknown[]): T
|
||||
}
|
||||
|
||||
export type MedusaContainer = AwilixContainer & {
|
||||
registerAdd: <T>(name: string, registration: T) => MedusaContainer
|
||||
}
|
||||
|
||||
export type Logger = _Logger & {
|
||||
progress: (activityId: string, msg: string) => void
|
||||
info: (msg: string) => void
|
||||
warn: (msg: string) => void
|
||||
}
|
||||
|
||||
export enum MODULE_SCOPE {
|
||||
INTERNAL = "internal",
|
||||
EXTERNAL = "external",
|
||||
}
|
||||
|
||||
export enum MODULE_RESOURCE_TYPE {
|
||||
SHARED = "shared",
|
||||
ISOLATED = "isolated",
|
||||
}
|
||||
|
||||
export type ConfigurableModuleDeclaration = {
|
||||
scope: MODULE_SCOPE.INTERNAL
|
||||
resources: MODULE_RESOURCE_TYPE
|
||||
resolve?: string
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
/*
|
||||
| {
|
||||
scope: MODULE_SCOPE.external
|
||||
server: {
|
||||
type: "built-in" | "rest" | "tsrpc" | "grpc" | "gql"
|
||||
url: string
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
export type ModuleResolution = {
|
||||
resolutionPath: string | false
|
||||
definition: ModuleDefinition
|
||||
options?: Record<string, unknown>
|
||||
moduleDeclaration?: ConfigurableModuleDeclaration
|
||||
}
|
||||
|
||||
export type ModuleDefinition = {
|
||||
key: string
|
||||
registrationName: string
|
||||
defaultPackage: string | false
|
||||
label: string
|
||||
canOverride?: boolean
|
||||
isRequired?: boolean
|
||||
defaultModuleDeclaration: ConfigurableModuleDeclaration
|
||||
}
|
||||
|
||||
export type LoaderOptions = {
|
||||
container: MedusaContainer
|
||||
configModule: ConfigModule
|
||||
options?: Record<string, unknown>
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
export type Constructor<T> = new (...args: any[]) => T
|
||||
|
||||
export type ModuleExports = {
|
||||
loaders: ((
|
||||
options: LoaderOptions,
|
||||
moduleDeclaration?: ConfigurableModuleDeclaration
|
||||
) => Promise<void>)[]
|
||||
service: Constructor<any>
|
||||
migrations?: any[]
|
||||
models?: Constructor<any>[]
|
||||
}
|
||||
|
||||
export type ConfigModule = {
|
||||
options?: Record<string, any>
|
||||
modules?: Record<
|
||||
string,
|
||||
false | string | Partial<ConfigurableModuleDeclaration>
|
||||
>
|
||||
moduleResolutions?: Record<string, ModuleResolution>
|
||||
}
|
||||
|
||||
export type ModulesResponse = {
|
||||
module: string
|
||||
resolution: string | false
|
||||
}[]
|
||||
@@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es5",
|
||||
"es6",
|
||||
"es2019"
|
||||
],
|
||||
"target": "es5",
|
||||
"lib": ["es2020"],
|
||||
"target": "es2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
@@ -19,14 +15,13 @@
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"downlevelIteration": true // to use ES5 specific tooling
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src/**/*", "index.d.ts"],
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"./dist/**/*",
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,17 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@medusajs/medusa": "^1.7.7",
|
||||
"@medusajs/types": "*",
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^25.5.4",
|
||||
"ts-jest": "^25.5.1",
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/modules-sdk": "*"
|
||||
"@medusajs/modules-sdk": "*",
|
||||
"@medusajs/utils": "^0.0.1",
|
||||
"awilix": "^8.0.0",
|
||||
"typeorm": "^0.3.11"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "tsc --build --watch",
|
||||
@@ -36,6 +39,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@medusajs/medusa": "^1.7.7",
|
||||
"typeorm": "^0.3.11"
|
||||
"@medusajs/types": "^0.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const CONNECTION_NAME = "stock_location_connection"
|
||||
@@ -1,13 +1,13 @@
|
||||
import ConnectionLoader from "./loaders/connection"
|
||||
import StockLocationService from "./services/stock-location"
|
||||
import * as SchemaMigration from "./migrations/schema-migrations/1665749860179-setup"
|
||||
import loadConnection from "./loaders/connection"
|
||||
|
||||
import migrations from "./migrations"
|
||||
import * as StockLocationModels from "./models"
|
||||
import StockLocationService from "./services/stock-location"
|
||||
|
||||
import { ModuleExports } from "@medusajs/modules-sdk"
|
||||
|
||||
const service = StockLocationService
|
||||
const migrations = [SchemaMigration]
|
||||
const loaders = [ConnectionLoader]
|
||||
|
||||
const loaders = [loadConnection]
|
||||
const models = Object.values(StockLocationModels)
|
||||
|
||||
const moduleDefinition: ModuleExports = {
|
||||
|
||||
@@ -1,9 +1,49 @@
|
||||
import {
|
||||
ConfigurableModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
LoaderOptions,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { DataSource, DataSourceOptions } from "typeorm"
|
||||
|
||||
import * as StockLocationModels from "../models"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { asValue } from "awilix"
|
||||
|
||||
export default async (
|
||||
{ configModule }: LoaderOptions,
|
||||
moduleDeclaration?: ConfigurableModuleDeclaration
|
||||
): Promise<void> => {}
|
||||
{ options, container }: LoaderOptions,
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
): Promise<void> => {
|
||||
if (
|
||||
moduleDeclaration?.scope === MODULE_SCOPE.INTERNAL &&
|
||||
moduleDeclaration.resources === MODULE_RESOURCE_TYPE.SHARED
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const dbData = options?.database as Record<string, string>
|
||||
|
||||
if (!dbData) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
`Database config is not present at module config "options.database"`
|
||||
)
|
||||
}
|
||||
|
||||
const entities = Object.values(StockLocationModels)
|
||||
const dataSource = new DataSource({
|
||||
type: dbData.database_type,
|
||||
url: dbData.database_url,
|
||||
database: dbData.database_database,
|
||||
extra: dbData.database_extra || {},
|
||||
schema: dbData.database_schema,
|
||||
entities,
|
||||
logging: dbData.database_logging,
|
||||
} as DataSourceOptions)
|
||||
|
||||
await dataSource.initialize()
|
||||
|
||||
container.register({
|
||||
manager: asValue(dataSource.manager),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import * as setup from "./schema-migrations/1665749860179-setup"
|
||||
|
||||
export default [setup]
|
||||
@@ -1,46 +1,4 @@
|
||||
import { ConfigModule } from "@medusajs/medusa"
|
||||
import {
|
||||
createConnection,
|
||||
ConnectionOptions,
|
||||
MigrationInterface,
|
||||
QueryRunner,
|
||||
} from "typeorm"
|
||||
|
||||
import { CONNECTION_NAME } from "../../config"
|
||||
|
||||
export const up = async ({ configModule }: { configModule: ConfigModule }) => {
|
||||
const connection = await createConnection({
|
||||
name: CONNECTION_NAME,
|
||||
type: configModule.projectConfig.database_type,
|
||||
url: configModule.projectConfig.database_url,
|
||||
database: configModule.projectConfig.database_database,
|
||||
schema: configModule.projectConfig.database_schema,
|
||||
extra: configModule.projectConfig.database_extra || {},
|
||||
migrations: [setup1665749860179],
|
||||
logging: true,
|
||||
} as ConnectionOptions)
|
||||
|
||||
await connection.runMigrations()
|
||||
}
|
||||
|
||||
export const down = async ({
|
||||
configModule,
|
||||
}: {
|
||||
configModule: ConfigModule
|
||||
}) => {
|
||||
const connection = await createConnection({
|
||||
name: CONNECTION_NAME,
|
||||
type: configModule.projectConfig.database_type,
|
||||
url: configModule.projectConfig.database_url,
|
||||
database: configModule.projectConfig.database_database,
|
||||
schema: configModule.projectConfig.database_schema,
|
||||
extra: configModule.projectConfig.database_extra || {},
|
||||
migrations: [setup1665749860179],
|
||||
logging: true,
|
||||
} as ConnectionOptions)
|
||||
|
||||
await connection.undoLastMigration({ transaction: "all" })
|
||||
}
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class setup1665749860179 implements MigrationInterface {
|
||||
name = "setup1665749860179"
|
||||
|
||||
@@ -6,18 +6,13 @@ import {
|
||||
IEventBusService,
|
||||
setMetadata,
|
||||
StockLocationAddressInput,
|
||||
TransactionBaseService,
|
||||
UpdateStockLocationInput,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
import {
|
||||
ConfigurableModuleDeclaration,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
} from "@medusajs/modules-sdk"
|
||||
|
||||
import { InternalModuleDeclaration } from "@medusajs/modules-sdk"
|
||||
import { SharedContext } from "@medusajs/types"
|
||||
import { InjectEntityManager, MedusaContext } from "@medusajs/utils"
|
||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
import { StockLocation, StockLocationAddress } from "../models"
|
||||
|
||||
type InjectedDependencies = {
|
||||
@@ -29,30 +24,22 @@ type InjectedDependencies = {
|
||||
* Service for managing stock locations.
|
||||
*/
|
||||
|
||||
export default class StockLocationService extends TransactionBaseService {
|
||||
export default class StockLocationService {
|
||||
static Events = {
|
||||
CREATED: "stock-location.created",
|
||||
UPDATED: "stock-location.updated",
|
||||
DELETED: "stock-location.deleted",
|
||||
}
|
||||
|
||||
protected readonly manager_: EntityManager
|
||||
protected readonly eventBusService_: IEventBusService
|
||||
|
||||
constructor(
|
||||
{ eventBusService }: InjectedDependencies,
|
||||
{ eventBusService, manager }: InjectedDependencies,
|
||||
options?: unknown,
|
||||
moduleDeclaration?: ConfigurableModuleDeclaration
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
|
||||
if (moduleDeclaration?.resources !== MODULE_RESOURCE_TYPE.SHARED) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"At the moment this module can only be used with shared resources"
|
||||
)
|
||||
}
|
||||
|
||||
this.manager_ = manager
|
||||
this.eventBusService_ = eventBusService
|
||||
}
|
||||
|
||||
@@ -62,11 +49,13 @@ export default class StockLocationService extends TransactionBaseService {
|
||||
* @param config - Additional configuration for the query.
|
||||
* @return A list of stock locations.
|
||||
*/
|
||||
@InjectEntityManager()
|
||||
async list(
|
||||
selector: FilterableStockLocationProps = {},
|
||||
config: FindConfig<StockLocation> = { relations: [], skip: 0, take: 10 }
|
||||
config: FindConfig<StockLocation> = { relations: [], skip: 0, take: 10 },
|
||||
@MedusaContext() context: SharedContext = {}
|
||||
): Promise<StockLocation[]> {
|
||||
const manager = this.activeManager_
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(StockLocation)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
@@ -79,11 +68,13 @@ export default class StockLocationService extends TransactionBaseService {
|
||||
* @param config - Additional configuration for the query.
|
||||
* @return A list of stock locations and the count of matching stock locations.
|
||||
*/
|
||||
@InjectEntityManager()
|
||||
async listAndCount(
|
||||
selector: FilterableStockLocationProps = {},
|
||||
config: FindConfig<StockLocation> = { relations: [], skip: 0, take: 10 }
|
||||
config: FindConfig<StockLocation> = { relations: [], skip: 0, take: 10 },
|
||||
@MedusaContext() context: SharedContext = {}
|
||||
): Promise<[StockLocation[], number]> {
|
||||
const manager = this.activeManager_
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(StockLocation)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
@@ -97,9 +88,11 @@ export default class StockLocationService extends TransactionBaseService {
|
||||
* @return The stock location.
|
||||
* @throws If the stock location ID is not definedor the stock location with the given ID was not found.
|
||||
*/
|
||||
@InjectEntityManager()
|
||||
async retrieve(
|
||||
stockLocationId: string,
|
||||
config: FindConfig<StockLocation> = {}
|
||||
config: FindConfig<StockLocation> = {},
|
||||
@MedusaContext() context: SharedContext = {}
|
||||
): Promise<StockLocation> {
|
||||
if (!isDefined(stockLocationId)) {
|
||||
throw new MedusaError(
|
||||
@@ -108,7 +101,7 @@ export default class StockLocationService extends TransactionBaseService {
|
||||
)
|
||||
}
|
||||
|
||||
const manager = this.activeManager_
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(StockLocation)
|
||||
|
||||
const query = buildQuery({ id: stockLocationId }, config)
|
||||
@@ -129,41 +122,44 @@ export default class StockLocationService extends TransactionBaseService {
|
||||
* @param data - The input data for creating a Stock Location.
|
||||
* @returns The created stock location.
|
||||
*/
|
||||
async create(data: CreateStockLocationInput): Promise<StockLocation> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const locationRepo = manager.getRepository(StockLocation)
|
||||
@InjectEntityManager()
|
||||
async create(
|
||||
data: CreateStockLocationInput,
|
||||
@MedusaContext() context: SharedContext = {}
|
||||
): Promise<StockLocation> {
|
||||
const manager = context.transactionManager!
|
||||
|
||||
const loc = locationRepo.create({
|
||||
name: data.name,
|
||||
const locationRepo = manager.getRepository(StockLocation)
|
||||
|
||||
const loc = locationRepo.create({
|
||||
name: data.name,
|
||||
})
|
||||
|
||||
if (isDefined(data.address) || isDefined(data.address_id)) {
|
||||
if (typeof data.address === "string" || data.address_id) {
|
||||
const addrId = (data.address ?? data.address_id) as string
|
||||
loc.address_id = addrId
|
||||
} else {
|
||||
const locAddressRepo = manager.getRepository(StockLocationAddress)
|
||||
const locAddress = locAddressRepo.create(data.address!)
|
||||
const addressResult = await locAddressRepo.save(locAddress)
|
||||
loc.address_id = addressResult.id
|
||||
}
|
||||
}
|
||||
|
||||
const { metadata } = data
|
||||
if (metadata) {
|
||||
loc.metadata = setMetadata(loc, metadata)
|
||||
}
|
||||
const result = await locationRepo.save(loc)
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(StockLocationService.Events.CREATED, {
|
||||
id: result.id,
|
||||
})
|
||||
|
||||
if (isDefined(data.address) || isDefined(data.address_id)) {
|
||||
if (typeof data.address === "string" || data.address_id) {
|
||||
const addrId = (data.address ?? data.address_id) as string
|
||||
loc.address_id = addrId
|
||||
} else {
|
||||
const locAddressRepo = manager.getRepository(StockLocationAddress)
|
||||
const locAddress = locAddressRepo.create(data.address!)
|
||||
const addressResult = await locAddressRepo.save(locAddress)
|
||||
loc.address_id = addressResult.id
|
||||
}
|
||||
}
|
||||
|
||||
const { metadata } = data
|
||||
if (metadata) {
|
||||
loc.metadata = setMetadata(loc, metadata)
|
||||
}
|
||||
|
||||
const result = await locationRepo.save(loc)
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(StockLocationService.Events.CREATED, {
|
||||
id: result.id,
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,46 +168,46 @@ export default class StockLocationService extends TransactionBaseService {
|
||||
* @param updateData - The update data for the stock location.
|
||||
* @returns The updated stock location.
|
||||
*/
|
||||
|
||||
@InjectEntityManager()
|
||||
async update(
|
||||
stockLocationId: string,
|
||||
updateData: UpdateStockLocationInput
|
||||
updateData: UpdateStockLocationInput,
|
||||
@MedusaContext() context: SharedContext = {}
|
||||
): Promise<StockLocation> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const locationRepo = manager.getRepository(StockLocation)
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(StockLocation)
|
||||
|
||||
const item = await this.retrieve(stockLocationId)
|
||||
const item = await this.retrieve(stockLocationId, undefined, context)
|
||||
|
||||
const { address, ...data } = updateData
|
||||
const { address, ...data } = updateData
|
||||
|
||||
if (address) {
|
||||
if (item.address_id) {
|
||||
await this.updateAddress(item.address_id, address)
|
||||
} else {
|
||||
const locAddressRepo = manager.getRepository(StockLocationAddress)
|
||||
const locAddress = locAddressRepo.create(address)
|
||||
const addressResult = await locAddressRepo.save(locAddress)
|
||||
data.address_id = addressResult.id
|
||||
}
|
||||
if (address) {
|
||||
if (item.address_id) {
|
||||
await this.updateAddress(item.address_id, address, context)
|
||||
} else {
|
||||
const locAddressRepo = manager.getRepository(StockLocationAddress)
|
||||
const locAddress = locAddressRepo.create(address)
|
||||
const addressResult = await locAddressRepo.save(locAddress)
|
||||
data.address_id = addressResult.id
|
||||
}
|
||||
}
|
||||
|
||||
const { metadata, ...fields } = data
|
||||
const { metadata, ...fields } = data
|
||||
|
||||
const toSave = locationRepo.merge(item, fields)
|
||||
if (metadata) {
|
||||
toSave.metadata = setMetadata(toSave, metadata)
|
||||
}
|
||||
const toSave = locationRepo.merge(item, fields)
|
||||
if (metadata) {
|
||||
toSave.metadata = setMetadata(toSave, metadata)
|
||||
}
|
||||
|
||||
await locationRepo.save(toSave)
|
||||
await locationRepo.save(toSave)
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(StockLocationService.Events.UPDATED, {
|
||||
id: stockLocationId,
|
||||
})
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(StockLocationService.Events.UPDATED, {
|
||||
id: stockLocationId,
|
||||
})
|
||||
|
||||
return item
|
||||
})
|
||||
return item
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,10 +216,11 @@ export default class StockLocationService extends TransactionBaseService {
|
||||
* @param address - The update data for the address.
|
||||
* @returns The updated stock location address.
|
||||
*/
|
||||
|
||||
@InjectEntityManager()
|
||||
protected async updateAddress(
|
||||
addressId: string,
|
||||
address: StockLocationAddressInput
|
||||
address: StockLocationAddressInput,
|
||||
@MedusaContext() context: SharedContext = {}
|
||||
): Promise<StockLocationAddress> {
|
||||
if (!isDefined(addressId)) {
|
||||
throw new MedusaError(
|
||||
@@ -232,28 +229,27 @@ export default class StockLocationService extends TransactionBaseService {
|
||||
)
|
||||
}
|
||||
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const locationAddressRepo = manager.getRepository(StockLocationAddress)
|
||||
const manager = context.transactionManager!
|
||||
const locationAddressRepo = manager.getRepository(StockLocationAddress)
|
||||
|
||||
const existingAddress = await locationAddressRepo.findOne({
|
||||
where: { id: addressId },
|
||||
})
|
||||
if (!existingAddress) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`StockLocation address with id ${addressId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
const { metadata, ...fields } = address
|
||||
|
||||
const toSave = locationAddressRepo.merge(existingAddress, fields)
|
||||
if (metadata) {
|
||||
toSave.metadata = setMetadata(toSave, metadata)
|
||||
}
|
||||
|
||||
return await locationAddressRepo.save(toSave)
|
||||
const existingAddress = await locationAddressRepo.findOne({
|
||||
where: { id: addressId },
|
||||
})
|
||||
if (!existingAddress) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`StockLocation address with id ${addressId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
const { metadata, ...fields } = address
|
||||
|
||||
const toSave = locationAddressRepo.merge(existingAddress, fields)
|
||||
if (metadata) {
|
||||
toSave.metadata = setMetadata(toSave, metadata)
|
||||
}
|
||||
|
||||
return await locationAddressRepo.save(toSave)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,17 +257,20 @@ export default class StockLocationService extends TransactionBaseService {
|
||||
* @param id - The ID of the stock location to delete.
|
||||
* @returns An empty promise.
|
||||
*/
|
||||
async delete(id: string): Promise<void> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const locationRepo = manager.getRepository(StockLocation)
|
||||
@InjectEntityManager()
|
||||
async delete(
|
||||
id: string,
|
||||
@MedusaContext() context: SharedContext = {}
|
||||
): Promise<void> {
|
||||
const manager = context.transactionManager!
|
||||
const locationRepo = manager.getRepository(StockLocation)
|
||||
|
||||
await locationRepo.softRemove({ id })
|
||||
await locationRepo.softRemove({ id })
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(StockLocationService.Events.DELETED, {
|
||||
id,
|
||||
})
|
||||
})
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(StockLocationService.Events.DELETED, {
|
||||
id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es5",
|
||||
"es6",
|
||||
"es2019"
|
||||
],
|
||||
"target": "es5",
|
||||
"lib": ["es2020"],
|
||||
"target": "es2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
@@ -19,14 +15,13 @@
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"downlevelIteration": true // to use ES5 specific tooling
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src/**/*", "index.d.ts"],
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"./dist/**/*",
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@medusajs/types",
|
||||
"version": "0.0.1",
|
||||
"description": "Medusa Types definition",
|
||||
"main": "dist/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/types"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": "Medusa",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"cross-env": "^5.2.1",
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||
"build": "tsc --build",
|
||||
"test": "exit 0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./shared-context"
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export type SharedContext = {
|
||||
transactionManager?: EntityManager
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2020"],
|
||||
"target": "es2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
tsConfig: "tsconfig.json",
|
||||
isolatedModules: false,
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.[jt]s?$": "ts-jest",
|
||||
},
|
||||
testEnvironment: `node`,
|
||||
moduleFileExtensions: [`js`, `ts`],
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@medusajs/utils",
|
||||
"version": "0.0.1",
|
||||
"description": "Medusa utilities functions shared by Medusa core and Modules",
|
||||
"main": "dist/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/utils"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": "Medusa",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@medusajs/types": "*",
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^25.5.4",
|
||||
"ts-jest": "^25.5.1",
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@medusajs/types": "^0.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||
"build": "tsc --build",
|
||||
"test": "jest --passWithNoTests src"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export function MedusaContext() {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string | symbol,
|
||||
parameterIndex: number
|
||||
) {
|
||||
if (!target.MedusaContext_) {
|
||||
target.MedusaContext_ = {}
|
||||
}
|
||||
|
||||
if (propertyKey in target.MedusaContext_) {
|
||||
throw new Error(
|
||||
`Only one MedusaContext is allowed on method "${String(propertyKey)}".`
|
||||
)
|
||||
}
|
||||
|
||||
target.MedusaContext_[propertyKey] = parameterIndex
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./context-parameter"
|
||||
export * from "./inject-entity-manager"
|
||||
@@ -0,0 +1,37 @@
|
||||
import { SharedContext } from "@medusajs/types"
|
||||
|
||||
export function InjectEntityManager(
|
||||
managerProperty = "manager_"
|
||||
): MethodDecorator {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: any
|
||||
): void {
|
||||
if (!target.MedusaContext_) {
|
||||
throw new Error(
|
||||
`To apply @InjectEntityManager you have to flag a parameter using @MedusaContext`
|
||||
)
|
||||
}
|
||||
|
||||
const originalMethod = descriptor.value
|
||||
|
||||
const argIndex = target.MedusaContext_[propertyKey]
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
const context: SharedContext = args[argIndex] ?? {}
|
||||
|
||||
if (context?.transactionManager) {
|
||||
return await originalMethod.apply(this, args)
|
||||
}
|
||||
|
||||
return await this[managerProperty].transaction(
|
||||
async (transactionManager) => {
|
||||
args[argIndex] = args[argIndex] ?? {}
|
||||
args[argIndex].transactionManager = transactionManager
|
||||
|
||||
return await originalMethod.apply(this, args)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./decorators"
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2020"],
|
||||
"target": "es2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -5742,6 +5742,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@medusajs/medusa": "*"
|
||||
"@medusajs/modules-sdk": "*"
|
||||
awilix: ^8.0.0
|
||||
cross-env: ^5.2.1
|
||||
jest: ^25.5.4
|
||||
ts-jest: ^25.5.1
|
||||
@@ -5749,7 +5750,6 @@ __metadata:
|
||||
typescript: ^4.4.4
|
||||
peerDependencies:
|
||||
"@medusajs/medusa": 1.7.13
|
||||
typeorm: ^0.3.11
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -5919,7 +5919,9 @@ __metadata:
|
||||
dependencies:
|
||||
awilix: ^8.0.0
|
||||
cross-env: ^5.2.1
|
||||
glob: 7.1.6
|
||||
jest: ^25.5.4
|
||||
medusa-core-utils: ^1.1.39
|
||||
medusa-telemetry: ^0.0.16
|
||||
resolve-cwd: ^3.0.0
|
||||
ts-jest: ^25.5.1
|
||||
@@ -5982,6 +5984,9 @@ __metadata:
|
||||
dependencies:
|
||||
"@medusajs/medusa": ^1.7.7
|
||||
"@medusajs/modules-sdk": "*"
|
||||
"@medusajs/types": "*"
|
||||
"@medusajs/utils": ^0.0.1
|
||||
awilix: ^8.0.0
|
||||
cross-env: ^5.2.1
|
||||
jest: ^25.5.4
|
||||
ts-jest: ^25.5.1
|
||||
@@ -5989,7 +5994,32 @@ __metadata:
|
||||
typescript: ^4.4.4
|
||||
peerDependencies:
|
||||
"@medusajs/medusa": ^1.7.7
|
||||
"@medusajs/types": ^0.0.1
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/types@*, @medusajs/types@workspace:packages/types":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/types@workspace:packages/types"
|
||||
dependencies:
|
||||
cross-env: ^5.2.1
|
||||
typeorm: ^0.3.11
|
||||
typescript: ^4.4.4
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/utils@^0.0.1, @medusajs/utils@workspace:packages/utils":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/utils@workspace:packages/utils"
|
||||
dependencies:
|
||||
"@medusajs/types": "*"
|
||||
cross-env: ^5.2.1
|
||||
jest: ^25.5.4
|
||||
ts-jest: ^25.5.1
|
||||
typeorm: ^0.3.11
|
||||
typescript: ^4.4.4
|
||||
peerDependencies:
|
||||
"@medusajs/types": ^0.0.1
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -26283,14 +26313,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"joi-objectid@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "joi-objectid@npm:3.0.1"
|
||||
checksum: 4820534ddff8779a7128661b8ed21bc7d552d53a8def60095db6e38f410c5893da21d7b91211d3af5dd5f6e4eda90a2f11b24a2e69efcef74c1a8249240ad83d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"joi@npm:^17.3.0, joi@npm:^17.4.2":
|
||||
"joi@npm:^17.4.2":
|
||||
version: 17.6.0
|
||||
resolution: "joi@npm:17.6.0"
|
||||
dependencies:
|
||||
@@ -28185,12 +28208,8 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "medusa-core-utils@workspace:packages/medusa-core-utils"
|
||||
dependencies:
|
||||
class-transformer: ^0.5.1
|
||||
class-validator: ^0.13.2
|
||||
cross-env: ^5.2.1
|
||||
jest: ^25.5.4
|
||||
joi: ^17.3.0
|
||||
joi-objectid: ^3.0.1
|
||||
ts-jest: ^25.5.1
|
||||
typescript: ^4.4.4
|
||||
languageName: unknown
|
||||
|
||||
Reference in New Issue
Block a user