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

* feat: scoped container for modules
This commit is contained in:
Carlos R. L. Rodrigues authored and GitHub committed 2023-03-15 12:09:45 -03:00
1 parent 8e78c533c4
commit 77d46220c2
65 files changed
+1116 -1011

No files matched your search

-1
View File
@@ -1 +0,0 @@
export const CONNECTION_NAME = "stock_location_connection"
+6 -6
View File
@@ -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,
})
}
}