feat: loosely typed container

This commit is contained in:
Harminder Virk
2024-05-31 15:22:03 +05:30
committed by GitHub
parent 2d956931b3
commit 11528526fa
41 changed files with 255 additions and 114 deletions
@@ -1,5 +1,5 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CreateStoreDTO, IStoreModuleService } from "@medusajs/types"
import { CreateStoreDTO, IStoreModuleService, StoreDTO } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { createStoresWorkflow } from "../../store"
@@ -16,8 +16,13 @@ export const createDefaultStoreStep = createStep(
let shouldDelete = false
let [store] = await storeService.list({}, { take: 1 })
/**
* @todo
* Seems like we are missing an integration test when the
* following conditional as true.
*/
if (!store) {
store = await createStoresWorkflow(container).run({
const stores = await createStoresWorkflow(container).run({
input: {
stores: [
{
@@ -30,6 +35,12 @@ export const createDefaultStoreStep = createStep(
},
})
/**
* As per types, the result from "createStoresWorkflow.run" was
* an array of "StoreDTO". But at runtime it turns out to be
* a "StoreDTO"
*/
store = stores as unknown as StoreDTO
shouldDelete = true
}
@@ -83,6 +83,7 @@ export const updateCartPromotionsStep = createStep(
: []
return new StepResponse(null, {
// @ts-expect-error
createdLinkIds: createdLinks.map((link) => link.id),
dismissedLinks: linksToDismiss,
})
@@ -95,6 +96,7 @@ export const updateCartPromotionsStep = createStep(
}
if (revertData?.createdLinkIds?.length) {
// @ts-expect-error
await remoteLink.delete(revertData.createdLinkIds)
}
}
@@ -42,6 +42,20 @@ export const adjustInventoryLevelsStep = createStep(
const inventoryService = container.resolve(ModuleRegistrationName.INVENTORY)
await inventoryService.adjustInventory(adjustedLevels)
/**
* @todo
* The method "adjustInventory" was broken, it was receiving the
* "inventoryItemId" and "locationId" as snake case, whereas
* the expected object needed these properties as camelCase
*/
await inventoryService.adjustInventory(
adjustedLevels.map((level) => {
return {
inventoryItemId: level.inventory_item_id,
locationId: level.location_id,
adjustment: level.adjustment,
}
})
)
}
)
@@ -20,7 +20,7 @@ export const attachInventoryItemToVariants = createStep(
.filter(({ tag }) => !!tag)
.map(({ inventoryItemId, tag }) => ({
productService: {
variant_id: tag,
variant_id: tag!,
},
inventoryService: {
inventory_item_id: inventoryItemId,
@@ -1,11 +1,5 @@
import {
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { InventoryNext } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
export const validateInventoryItemsForCreateStepId =
@@ -25,7 +19,7 @@ export const validateInventoryItemsForCreate = createStep(
"variant_id",
Modules.INVENTORY,
"inventory_item_id"
)
)!
const existingItems = await linkService.list(
{ variant_id: input.map((i) => i.tag) },
@@ -33,10 +27,12 @@ export const validateInventoryItemsForCreate = createStep(
)
if (existingItems.length) {
// @ts-expect-error
const ids = existingItems.map((i) => i.variant_id).join(", ")
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Inventory items already exist for variants with ids: " +
existingItems.map((i) => i.variant_id).join(", ")
`Inventory items already exist for variants with ids: ${ids}`
)
}
@@ -26,12 +26,15 @@ export const getVariantPricingLinkStep = createStep(
"variant_id",
Modules.PRICING,
"price_set_id"
)
)!
const existingItems = await linkService.list(
const existingItems = (await linkService.list(
{ variant_id: data.ids },
{ select: ["variant_id", "price_set_id"] }
)
)) as {
variant_id: string
price_set_id: string
}[]
if (existingItems.length !== data.ids.length) {
const missing = arrayDifference(
@@ -78,16 +78,22 @@ export const updateProductVariantsWorkflow = createWorkflow(
}
if ("product_variants" in data.input) {
return data.variantPriceSetLinks.map((link) => {
const variant = (data.input as any).product_variants.find(
(v) => v.id === link.variant_id
)
return data.variantPriceSetLinks
.map((link) => {
if (!("product_variants" in data.input)) {
return
}
return {
id: link.price_set_id,
prices: variant.prices,
} as PricingTypes.UpsertPriceSetDTO
})
const variant = data.input.product_variants.find(
(v) => v.id === link.variant_id
)!
return {
id: link.price_set_id,
prices: variant.prices,
} as PricingTypes.UpsertPriceSetDTO
})
.filter(Boolean)
}
return {
@@ -1,4 +1,4 @@
import { Modules } from "@medusajs/modules-sdk"
import { Modules, RemoteLink } from "@medusajs/modules-sdk"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
@@ -17,6 +17,6 @@ export const createUsersStep = createStep(
return
}
const service = container.resolve(ModuleRegistrationName.USER)
await service.delete(createdUsers)
await service.delete(createdUsers.map((user) => user.id))
}
)
@@ -1,5 +1,6 @@
import {
Constructor,
IModuleService,
InternalModuleDeclaration,
Logger,
MedusaContainer,
@@ -158,9 +159,9 @@ export async function loadInternalModule(
if (loaderOnly) {
// The expectation is only to run the loader as standalone, so we do not need to register the service and we need to cleanup all services
const service = container.resolve(registrationName)
await service.__hooks?.onApplicationPrepareShutdown()
await service.__hooks?.onApplicationShutdown()
const service = container.resolve<IModuleService>(registrationName)
await service.__hooks?.onApplicationPrepareShutdown?.()
await service.__hooks?.onApplicationShutdown?.()
}
}
+14 -1
View File
@@ -1,10 +1,13 @@
import type { Knex } from "knex"
import { mergeTypeDefs } from "@graphql-tools/merge"
import { makeExecutableSchema } from "@graphql-tools/schema"
import { RemoteFetchDataCallback } from "@medusajs/orchestration"
import {
import type {
ConfigModule,
ExternalModuleDeclaration,
InternalModuleDeclaration,
LoadedModule,
Logger,
MedusaContainer,
ModuleDefinition,
ModuleExports,
@@ -36,6 +39,16 @@ import { cleanGraphQLSchema } from "./utils"
const LinkModulePackage = MODULE_PACKAGE_NAMES[Modules.LINK]
declare module "@medusajs/types" {
export interface ModuleImplementations {
[ContainerRegistrationKeys.REMOTE_LINK]: RemoteLink
[ContainerRegistrationKeys.CONFIG_MODULE]: ConfigModule
[ContainerRegistrationKeys.PG_CONNECTION]: Knex<any>
[ContainerRegistrationKeys.REMOTE_QUERY]: RemoteQueryFunction
[ContainerRegistrationKeys.LOGGER]: Logger
}
}
export type RunMigrationFn = (
options?: ModuleServiceInitializeOptions,
injectedDependencies?: Record<any, any>
+1
View File
@@ -29,3 +29,4 @@ export * as TaxTypes from "./tax"
export * as TransactionBaseTypes from "./transaction-base"
export * as UserTypes from "./user"
export * as WorkflowTypes from "./workflow"
export * as WorkflowsSdkTypes from "./workflows-sdk"
@@ -1,19 +1,32 @@
import { AwilixContainer } from "awilix"
import { AwilixContainer, ResolveOptions } from "awilix"
/**
* The Medusa Container extends [Awilix](https://github.com/jeffijoe/awilix) to
* The following interface acts as a bucket that other modules or the
* utils package can fill using declaration merging
*/
export interface ModuleImplementations {}
/**
* The Medusa Container extends [Awilix](https://github.com/jeffijoe/awilix) to
* provide dependency injection functionalities.
*/
export type MedusaContainer = AwilixContainer & {
/**
* @ignore
*/
registerAdd: <T>(name: string, registration: T) => MedusaContainer
/**
* @ignore
*/
createScope: () => MedusaContainer
}
export type MedusaContainer<Cradle extends object = ModuleImplementations> =
Omit<AwilixContainer, "resolve"> & {
resolve<K extends keyof Cradle>(
key: K,
resolveOptions?: ResolveOptions
): Cradle[K]
resolve<T>(key: string, resolveOptions?: ResolveOptions): T
/**
* @ignore
*/
registerAdd: <T>(name: string, registration: T) => MedusaContainer
/**
* @ignore
*/
createScope: () => MedusaContainer
}
export type ContainerLike = {
resolve<T = unknown>(key: string): T
+1
View File
@@ -40,3 +40,4 @@ export * from "./transaction-base"
export * from "./user"
export * from "./workflow"
export * from "./workflows"
export * from "./workflows-sdk"
@@ -282,7 +282,7 @@ export interface IStockLocationServiceNext extends IModuleService {
* @example
* await stockLocationModuleService.delete("sloc_123")
*/
delete(id: string, context?: Context): Promise<void>
delete(id: string | string[], context?: Context): Promise<void>
/**
* This method soft deletes stock locations by their IDs.
+1 -1
View File
@@ -16,7 +16,7 @@ import { IModuleService } from "../modules-sdk"
export interface IUserModuleService extends IModuleService {
/**
* This method validates that a token belongs to an invite and returns that invite.
*
*
* An error is thrown if the invite has expired or no invite matches the token.
*
* @param {string} token - The token to validate
@@ -1,4 +1,4 @@
import { BaseFilterable, OperatorMap } from "@medusajs/types"
import { BaseFilterable, OperatorMap } from "../dal"
export interface WorkflowExecutionDTO {
id: string
@@ -1,10 +1,6 @@
import {
ContainerLike,
Context,
FindConfig,
IModuleService,
} from "@medusajs/types"
import { ReturnWorkflow, UnwrapWorkflowInputDataType } from "../utils/composer"
import { ContainerLike, FindConfig } from "../common"
import { IModuleService } from "../modules-sdk"
import { Context } from "../shared-context"
import {
FilterableWorkflowExecutionProps,
WorkflowExecutionDTO,
@@ -55,14 +51,7 @@ export interface IWorkflowEngineService extends IModuleService {
sharedContext?: Context
): Promise<[WorkflowExecutionDTO[], number]>
run<
TWorkflow extends ReturnWorkflow<any, any, any> = ReturnWorkflow<
any,
any,
any
>,
TData = UnwrapWorkflowInputDataType<TWorkflow>
>(
run(
workflowId: string,
options?: WorkflowOrchestratorRunDTO,
sharedContext?: Context
+1 -1
View File
@@ -6,4 +6,4 @@ export const ContainerRegistrationKeys = {
REMOTE_QUERY: "remoteQuery",
REMOTE_LINK: "remoteLink",
FEATURE_FLAG_ROUTER: "featureFlagRouter",
}
} as const
@@ -1,3 +1,29 @@
import type {
IApiKeyModuleService,
IAuthModuleService,
ICacheService,
ICartModuleService,
ICurrencyModuleService,
ICustomerModuleService,
IEventBusModuleService,
IFileModuleService,
IFulfillmentModuleService,
IInventoryServiceNext,
INotificationModuleService,
IOrderModuleService,
IPaymentModuleService,
IPricingModuleService,
IProductModuleService,
IPromotionModuleService,
IRegionModuleService,
ISalesChannelModuleService,
IStockLocationServiceNext,
IStoreModuleService,
ITaxModuleService,
IUserModuleService,
IWorkflowEngineService,
} from "@medusajs/types"
export enum Modules {
AUTH = "auth",
CACHE = "cacheService",
@@ -24,3 +50,57 @@ export enum Modules {
FILE = "file",
NOTIFICATION = "notification",
}
export enum ModuleRegistrationName {
AUTH = "authModuleService",
CACHE = "cacheService",
CART = "cartModuleService",
CUSTOMER = "customerModuleService",
EVENT_BUS = "eventBusModuleService",
INVENTORY = "inventoryService",
PAYMENT = "paymentModuleService",
PRICING = "pricingModuleService",
PRODUCT = "productModuleService",
PROMOTION = "promotionModuleService",
SALES_CHANNEL = "salesChannelModuleService",
FULFILLMENT = "fulfillmentModuleService",
STOCK_LOCATION = "stockLocationService",
TAX = "taxModuleService",
USER = "userModuleService",
WORKFLOW_ENGINE = "workflowsModuleService",
REGION = "regionModuleService",
ORDER = "orderModuleService",
API_KEY = "apiKeyModuleService",
STORE = "storeModuleService",
CURRENCY = "currencyModuleService",
FILE = "fileModuleService",
NOTIFICATION = "notificationModuleService",
}
declare module "@medusajs/types" {
export interface ModuleImplementations {
[ModuleRegistrationName.AUTH]: IAuthModuleService
[ModuleRegistrationName.CACHE]: ICacheService
[ModuleRegistrationName.CART]: ICartModuleService
[ModuleRegistrationName.CUSTOMER]: ICustomerModuleService
[ModuleRegistrationName.EVENT_BUS]: IEventBusModuleService
[ModuleRegistrationName.INVENTORY]: IInventoryServiceNext
[ModuleRegistrationName.PAYMENT]: IPaymentModuleService
[ModuleRegistrationName.PRICING]: IPricingModuleService
[ModuleRegistrationName.PRODUCT]: IProductModuleService
[ModuleRegistrationName.PROMOTION]: IPromotionModuleService
[ModuleRegistrationName.SALES_CHANNEL]: ISalesChannelModuleService
[ModuleRegistrationName.TAX]: ITaxModuleService
[ModuleRegistrationName.FULFILLMENT]: IFulfillmentModuleService
[ModuleRegistrationName.STOCK_LOCATION]: IStockLocationServiceNext
[ModuleRegistrationName.USER]: IUserModuleService
[ModuleRegistrationName.WORKFLOW_ENGINE]: IWorkflowEngineService
[ModuleRegistrationName.REGION]: IRegionModuleService
[ModuleRegistrationName.ORDER]: IOrderModuleService
[ModuleRegistrationName.API_KEY]: IApiKeyModuleService
[ModuleRegistrationName.STORE]: IStoreModuleService
[ModuleRegistrationName.CURRENCY]: ICurrencyModuleService
[ModuleRegistrationName.FILE]: IFileModuleService
[ModuleRegistrationName.NOTIFICATION]: INotificationModuleService
}
}
-2
View File
@@ -1,6 +1,4 @@
export * from "./helper"
export * from "./medusa-workflow"
export * as WorkflowOrchestratorTypes from "./types"
export { IWorkflowEngineService } from "./types/service"
export * from "./utils/composer"
export * as Composer from "./utils/composer"