committed by
GitHub
parent
95c538c675
commit
5a8a889c6d
@@ -11,12 +11,12 @@ export const initialize = async (
|
||||
options?: InMemoryCacheModuleOptions | ExternalModuleDeclaration
|
||||
): Promise<ICacheService> => {
|
||||
const serviceKey = Modules.CACHE
|
||||
const loaded = await MedusaModule.bootstrap(
|
||||
const loaded = await MedusaModule.bootstrap<ICacheService>(
|
||||
serviceKey,
|
||||
"@medusajs/cache-inmemory",
|
||||
options as InternalModuleDeclaration | ExternalModuleDeclaration,
|
||||
undefined
|
||||
)
|
||||
|
||||
return loaded[serviceKey] as ICacheService
|
||||
return loaded[serviceKey]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src"],
|
||||
"include": ["src", "integration-tests"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ export const initialize = async (
|
||||
options?: RedisCacheModuleOptions | ExternalModuleDeclaration
|
||||
): Promise<ICacheService> => {
|
||||
const serviceKey = Modules.CACHE
|
||||
const loaded = await MedusaModule.bootstrap(
|
||||
const loaded = await MedusaModule.bootstrap<ICacheService>(
|
||||
serviceKey,
|
||||
"@medusajs/cache-redis",
|
||||
options as InternalModuleDeclaration | ExternalModuleDeclaration,
|
||||
undefined
|
||||
)
|
||||
|
||||
return loaded[serviceKey] as ICacheService
|
||||
return loaded[serviceKey]
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ import { IEventBusService } from "@medusajs/types"
|
||||
|
||||
export const initialize = async (): Promise<IEventBusService> => {
|
||||
const serviceKey = Modules.EVENT_BUS
|
||||
const loaded = await MedusaModule.bootstrap(
|
||||
const loaded = await MedusaModule.bootstrap<IEventBusService>(
|
||||
serviceKey,
|
||||
"@medusajs/event-bus-local"
|
||||
)
|
||||
|
||||
return loaded[serviceKey] as IEventBusService
|
||||
return loaded[serviceKey]
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ export const initialize = async (
|
||||
options?: EventBusRedisModuleOptions | ExternalModuleDeclaration
|
||||
): Promise<IEventBusService> => {
|
||||
const serviceKey = Modules.EVENT_BUS
|
||||
const loaded = await MedusaModule.bootstrap(
|
||||
const loaded = await MedusaModule.bootstrap<IEventBusService>(
|
||||
serviceKey,
|
||||
"@medusajs/event-bus-redis",
|
||||
options as InternalModuleDeclaration | ExternalModuleDeclaration,
|
||||
undefined
|
||||
)
|
||||
|
||||
return loaded[serviceKey] as IEventBusService
|
||||
return loaded[serviceKey]
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const initialize = async (
|
||||
}
|
||||
): Promise<IInventoryService> => {
|
||||
const serviceKey = Modules.INVENTORY
|
||||
const loaded = await MedusaModule.bootstrap(
|
||||
const loaded = await MedusaModule.bootstrap<IInventoryService>(
|
||||
serviceKey,
|
||||
"@medusajs/inventory",
|
||||
options as InternalModuleDeclaration | ExternalModuleDeclaration,
|
||||
@@ -23,5 +23,5 @@ export const initialize = async (
|
||||
injectedDependencies
|
||||
)
|
||||
|
||||
return loaded[serviceKey] as IInventoryService
|
||||
return loaded[serviceKey]
|
||||
}
|
||||
|
||||
51
packages/inventory/src/joiner-config.ts
Normal file
51
packages/inventory/src/joiner-config.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { JoinerServiceConfig } from "@medusajs/types"
|
||||
|
||||
export const joinerConfig: JoinerServiceConfig = {
|
||||
serviceName: Modules.INVENTORY,
|
||||
primaryKeys: ["id"],
|
||||
alias: [
|
||||
{
|
||||
name: "inventory_items",
|
||||
},
|
||||
{
|
||||
name: "inventory",
|
||||
},
|
||||
{
|
||||
name: "inventory_level",
|
||||
args: {
|
||||
methodSuffix: "InventoryLevels",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inventory_levels",
|
||||
args: {
|
||||
methodSuffix: "InventoryLevels",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reservation_items",
|
||||
args: {
|
||||
methodSuffix: "ReservationItems",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reservation_item",
|
||||
args: {
|
||||
methodSuffix: "ReservationItems",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reservation",
|
||||
args: {
|
||||
methodSuffix: "ReservationItems",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reservations",
|
||||
args: {
|
||||
methodSuffix: "ReservationItems",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
IInventoryService,
|
||||
InventoryItemDTO,
|
||||
InventoryLevelDTO,
|
||||
JoinerServiceConfig,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
ReservationItemDTO,
|
||||
SharedContext,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
MedusaError,
|
||||
} from "@medusajs/utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { joinerConfig } from "../joiner-config"
|
||||
import InventoryItemService from "./inventory-item"
|
||||
import InventoryLevelService from "./inventory-level"
|
||||
import ReservationItemService from "./reservation-item"
|
||||
@@ -57,6 +59,10 @@ export default class InventoryService implements IInventoryService {
|
||||
this.reservationItemService_ = reservationItemService
|
||||
}
|
||||
|
||||
__joinerConfig(): JoinerServiceConfig {
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists inventory items that match the given selector
|
||||
* @param selector - the selector to filter inventory items by
|
||||
@@ -75,6 +81,13 @@ export default class InventoryService implements IInventoryService {
|
||||
context
|
||||
)
|
||||
}
|
||||
async list(
|
||||
selector: FilterableInventoryItemProps,
|
||||
config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 },
|
||||
context: SharedContext = {}
|
||||
): Promise<InventoryItemDTO[]> {
|
||||
return await this.inventoryItemService_.list(selector, config, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists inventory levels that match the given selector
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@medusajs/types": "^1.8.7",
|
||||
"medusa-interfaces": "^1.3.7",
|
||||
"typeorm": "^0.3.16"
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("models loader", () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
try {
|
||||
models = modelsLoader({
|
||||
models = await modelsLoader({
|
||||
container,
|
||||
isTest: true,
|
||||
coreTestPathGlob: "../models/{product,product-variant}.ts",
|
||||
@@ -30,9 +30,9 @@ describe("models loader", () => {
|
||||
})
|
||||
|
||||
it("ensure that the product model is an extended model", () => {
|
||||
const productModel = container.resolve("productModel")
|
||||
const productModel = models.find((model) => model.name === "Product")
|
||||
|
||||
expect(productModel.custom_attribute).toEqual("test")
|
||||
expect(new productModel().custom_attribute).toEqual("test")
|
||||
})
|
||||
|
||||
it("ensure that the extended product model is registered in db_entities", () => {
|
||||
|
||||
@@ -8,7 +8,6 @@ import path from "path"
|
||||
import { ClassConstructor, MedusaContainer } from "../types/global"
|
||||
import { EntitySchema } from "typeorm"
|
||||
import { asClass, asValue } from "awilix"
|
||||
import { upperCaseFirst } from "@medusajs/utils"
|
||||
|
||||
type ModelLoaderParams = {
|
||||
container: MedusaContainer
|
||||
@@ -63,9 +62,8 @@ export default (
|
||||
// If an extension file is found, override it with that instead
|
||||
if (mappedExtensionModel) {
|
||||
const coreModel = require(modelPath)
|
||||
const modelName = upperCaseFirst(
|
||||
const modelName =
|
||||
formatRegistrationNameWithoutNamespace(modelPath)
|
||||
)
|
||||
|
||||
coreModel[modelName] = mappedExtensionModel
|
||||
val = mappedExtensionModel
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
import { SearchUtils, upperCaseFirst } from "@medusajs/utils"
|
||||
import { aliasTo, asFunction, asValue, Lifetime } from "awilix"
|
||||
import { Express } from "express"
|
||||
import fs from "fs"
|
||||
import { sync as existsSync } from "fs-exists-cached"
|
||||
import glob from "glob"
|
||||
import _ from "lodash"
|
||||
import { createRequireFromPath } from "medusa-core-utils"
|
||||
import {
|
||||
FileService,
|
||||
FulfillmentService,
|
||||
OauthService,
|
||||
} from "medusa-interfaces"
|
||||
import { trackInstallation } from "medusa-telemetry"
|
||||
import path from "path"
|
||||
import { EntitySchema } from "typeorm"
|
||||
import {
|
||||
AbstractTaxService,
|
||||
isBatchJobStrategy,
|
||||
@@ -23,23 +7,40 @@ import {
|
||||
isPriceSelectionStrategy,
|
||||
isTaxCalculationStrategy,
|
||||
} from "../interfaces"
|
||||
import { MiddlewareService } from "../services"
|
||||
import {
|
||||
ClassConstructor,
|
||||
ConfigModule,
|
||||
Logger,
|
||||
MedusaContainer,
|
||||
} from "../types/global"
|
||||
import {
|
||||
FileService,
|
||||
FulfillmentService,
|
||||
OauthService,
|
||||
} from "medusa-interfaces"
|
||||
import { Lifetime, aliasTo, asFunction, asValue } from "awilix"
|
||||
import { SearchUtils, upperCaseFirst } from "@medusajs/utils"
|
||||
import {
|
||||
formatRegistrationName,
|
||||
formatRegistrationNameWithoutNamespace,
|
||||
} from "../utils/format-registration-name"
|
||||
import { getModelExtensionsMap } from "./helpers/get-model-extension-map"
|
||||
import {
|
||||
registerPaymentProcessorFromClass,
|
||||
registerPaymentServiceFromClass,
|
||||
} from "./helpers/plugins"
|
||||
|
||||
import { EntitySchema } from "typeorm"
|
||||
import { Express } from "express"
|
||||
import { MiddlewareService } from "../services"
|
||||
import _ from "lodash"
|
||||
import { createRequireFromPath } from "medusa-core-utils"
|
||||
import { sync as existsSync } from "fs-exists-cached"
|
||||
import fs from "fs"
|
||||
import { getModelExtensionsMap } from "./helpers/get-model-extension-map"
|
||||
import glob from "glob"
|
||||
import logger from "./logger"
|
||||
import path from "path"
|
||||
import { trackInstallation } from "medusa-telemetry"
|
||||
|
||||
type Options = {
|
||||
rootDirectory: string
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/types": "^1.8.8",
|
||||
"@medusajs/orchestration": "^0.0.2",
|
||||
"@medusajs/types": "^1.8.11",
|
||||
"@medusajs/utils": "^1.9.1",
|
||||
"awilix": "^8.0.0",
|
||||
"resolve-cwd": "^3.0.0"
|
||||
|
||||
@@ -9,7 +9,7 @@ export enum Modules {
|
||||
STOCK_LOCATION = "stockLocationService",
|
||||
INVENTORY = "inventoryService",
|
||||
CACHE = "cacheService",
|
||||
PRODUCT = "productModuleService",
|
||||
PRODUCT = "productService",
|
||||
}
|
||||
|
||||
export const ModulesDefinition: { [key: string]: ModuleDefinition } = {
|
||||
@@ -33,6 +33,7 @@ export const ModulesDefinition: { [key: string]: ModuleDefinition } = {
|
||||
label: "StockLocationService",
|
||||
isRequired: false,
|
||||
canOverride: true,
|
||||
isQueryable: true,
|
||||
dependencies: ["eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
@@ -46,6 +47,7 @@ export const ModulesDefinition: { [key: string]: ModuleDefinition } = {
|
||||
label: "InventoryService",
|
||||
isRequired: false,
|
||||
canOverride: true,
|
||||
isQueryable: true,
|
||||
dependencies: ["eventBusService"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
@@ -66,11 +68,12 @@ export const ModulesDefinition: { [key: string]: ModuleDefinition } = {
|
||||
},
|
||||
[Modules.PRODUCT]: {
|
||||
key: Modules.PRODUCT,
|
||||
registrationName: Modules.PRODUCT,
|
||||
registrationName: "productModuleService",
|
||||
defaultPackage: false,
|
||||
label: "ProductModuleService",
|
||||
isRequired: false,
|
||||
canOverride: true,
|
||||
isQueryable: true,
|
||||
dependencies: [],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.EXTERNAL,
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from "./definitions"
|
||||
export * from "./loaders"
|
||||
export * from "./medusa-module"
|
||||
export * from "./module-helper"
|
||||
export * from "./remote-query"
|
||||
|
||||
@@ -3,27 +3,49 @@ import {
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
} from "@medusajs/types"
|
||||
import { MedusaModule } from "../../medusa-module"
|
||||
|
||||
const mockRegisterMedusaModule = jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve([]))
|
||||
const mockModuleLoader = jest.fn().mockImplementation(() => Promise.resolve({}))
|
||||
import { MedusaModule } from "../../medusa-module"
|
||||
import { asValue } from "awilix"
|
||||
|
||||
const mockRegisterMedusaModule = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
moduleKey: {
|
||||
definition: {
|
||||
key: "moduleKey",
|
||||
registrationName: "moduleKey",
|
||||
},
|
||||
moduleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const mockModuleLoader = jest.fn().mockImplementation(({ container }) => {
|
||||
container.register({
|
||||
moduleKey: asValue({}),
|
||||
})
|
||||
return Promise.resolve({})
|
||||
})
|
||||
|
||||
jest.mock("./../../loaders", () => ({
|
||||
registerMedusaModule: jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => mockRegisterMedusaModule()),
|
||||
moduleLoader: jest.fn().mockImplementation((...args) => mockModuleLoader()),
|
||||
moduleLoader: jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => mockModuleLoader.apply(this, args)),
|
||||
}))
|
||||
|
||||
describe("Medusa Module", () => {
|
||||
describe("Medusa Modules", () => {
|
||||
beforeEach(() => {
|
||||
MedusaModule.clearInstances()
|
||||
jest.resetModules()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("MedusaModule bootstrap - Singleton instances", async () => {
|
||||
it("should create singleton instances", async () => {
|
||||
await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
@@ -57,4 +79,182 @@ describe("Medusa Module", () => {
|
||||
expect(mockRegisterMedusaModule).toBeCalledTimes(2)
|
||||
expect(mockModuleLoader).toBeCalledTimes(2)
|
||||
})
|
||||
|
||||
it("should prevent the module being loaded multiple times under concurrent requests", async () => {
|
||||
const load: any = []
|
||||
|
||||
for (let i = 5; i--; ) {
|
||||
load.push(
|
||||
MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
)
|
||||
}
|
||||
|
||||
const intances = Promise.all(load)
|
||||
|
||||
expect(mockRegisterMedusaModule).toBeCalledTimes(1)
|
||||
expect(mockModuleLoader).toBeCalledTimes(1)
|
||||
expect(intances[(await intances).length - 1]).toBe(intances[0])
|
||||
})
|
||||
|
||||
it("getModuleInstance should return the first instance of the module if there is none flagged as 'main'", async () => {
|
||||
const moduleA = await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
const moduleB = await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
expect(MedusaModule.getModuleInstance("moduleKey")).toEqual(moduleA)
|
||||
})
|
||||
|
||||
it("should return the module flagged as 'main' when multiple instances are available", async () => {
|
||||
const moduleA = await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
const moduleB = await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
main: true,
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
expect(MedusaModule.getModuleInstance("moduleKey")).toEqual(moduleB)
|
||||
})
|
||||
|
||||
it("should retrieve the module by their given alias", async () => {
|
||||
const moduleA = await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "mod_A",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
const moduleB = await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
main: true,
|
||||
alias: "mod_B",
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
const moduleC = await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "mod_C",
|
||||
options: {
|
||||
moduleC: true,
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
// main
|
||||
expect(MedusaModule.getModuleInstance("moduleKey")).toEqual(moduleB)
|
||||
|
||||
expect(MedusaModule.getModuleInstance("moduleKey", "mod_A")).toEqual(
|
||||
moduleA
|
||||
)
|
||||
expect(MedusaModule.getModuleInstance("moduleKey", "mod_B")).toEqual(
|
||||
moduleB
|
||||
)
|
||||
expect(MedusaModule.getModuleInstance("moduleKey", "mod_C")).toEqual(
|
||||
moduleC
|
||||
)
|
||||
})
|
||||
|
||||
it("should prevent two main modules being set as 'main'", async () => {
|
||||
await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "mod_A",
|
||||
options: {
|
||||
abc: 123,
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
main: true,
|
||||
alias: "mod_B",
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
const moduleC = MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
main: true,
|
||||
alias: "mod_C",
|
||||
options: {
|
||||
moduleC: true,
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
expect(moduleC).rejects.toThrow(
|
||||
"Module moduleKey already have a 'main' registered."
|
||||
)
|
||||
})
|
||||
|
||||
it("should prevent the same alias be used for different instances of the same module", async () => {
|
||||
await MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "module_alias",
|
||||
options: {
|
||||
different_options: "abc",
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
const moduleC = MedusaModule.bootstrap("moduleKey", "@path", {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: "@path",
|
||||
alias: "module_alias",
|
||||
options: {
|
||||
moduleC: true,
|
||||
},
|
||||
} as InternalModuleDeclaration)
|
||||
|
||||
expect(moduleC).rejects.toThrow(
|
||||
"Module moduleKey already registed as 'module_alias'. Please choose a different alias."
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
JoinerServiceConfig,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
MODULE_SCOPE,
|
||||
ModuleDefinition,
|
||||
ModuleExports,
|
||||
ModuleResolution,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
createMedusaContainer,
|
||||
simpleHash,
|
||||
stringifyCircular,
|
||||
} from "@medusajs/utils"
|
||||
import { asValue } from "awilix"
|
||||
import { moduleLoader, registerMedusaModule } from "./loaders"
|
||||
|
||||
import { asValue } from "awilix"
|
||||
import { loadModuleMigrations } from "./loaders/utils"
|
||||
|
||||
const logger: any = {
|
||||
@@ -21,19 +25,105 @@ const logger: any = {
|
||||
error: (a) => console.error(a),
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface MedusaModule {
|
||||
getLoadedModules(): Map<string, any>
|
||||
}
|
||||
}
|
||||
|
||||
type ModuleAlias = {
|
||||
key: string
|
||||
hash: string
|
||||
alias?: string
|
||||
main?: boolean
|
||||
}
|
||||
|
||||
export class MedusaModule {
|
||||
private static instances_: Map<string, any> = new Map()
|
||||
private static modules_: Map<string, ModuleAlias[]> = new Map()
|
||||
private static loading_: Map<string, Promise<any>> = new Map()
|
||||
|
||||
public static getLoadedModules(): Map<
|
||||
string,
|
||||
any & {
|
||||
__joinerConfig: JoinerServiceConfig
|
||||
__definition: ModuleDefinition
|
||||
}
|
||||
> {
|
||||
return MedusaModule.instances_
|
||||
}
|
||||
|
||||
public static clearInstances(): void {
|
||||
MedusaModule.instances_.clear()
|
||||
MedusaModule.modules_.clear()
|
||||
}
|
||||
public static async bootstrap(
|
||||
|
||||
public static isInstalled(moduleKey: string, alias?: string): boolean {
|
||||
if (alias) {
|
||||
return (
|
||||
MedusaModule.modules_.has(moduleKey) &&
|
||||
MedusaModule.modules_.get(moduleKey)!.some((m) => m.alias === alias)
|
||||
)
|
||||
}
|
||||
|
||||
return MedusaModule.modules_.has(moduleKey)
|
||||
}
|
||||
|
||||
public static getModuleInstance(
|
||||
moduleKey: string,
|
||||
alias?: string
|
||||
): any | undefined {
|
||||
if (!MedusaModule.modules_.has(moduleKey)) {
|
||||
return
|
||||
}
|
||||
|
||||
let mod
|
||||
const modules = MedusaModule.modules_.get(moduleKey)!
|
||||
if (alias) {
|
||||
mod = modules.find((m) => m.alias === alias)
|
||||
|
||||
return MedusaModule.instances_.get(mod?.hash)
|
||||
}
|
||||
|
||||
mod = modules.find((m) => m.main) ?? modules[0]
|
||||
|
||||
return MedusaModule.instances_.get(mod?.hash)
|
||||
}
|
||||
|
||||
private static registerModule(
|
||||
moduleKey: string,
|
||||
loadedModule: ModuleAlias
|
||||
): void {
|
||||
if (!MedusaModule.modules_.has(moduleKey)) {
|
||||
MedusaModule.modules_.set(moduleKey, [])
|
||||
}
|
||||
|
||||
const modules = MedusaModule.modules_.get(moduleKey)!
|
||||
|
||||
if (modules.some((m) => m.alias === loadedModule.alias)) {
|
||||
throw new Error(
|
||||
`Module ${moduleKey} already registed as '${loadedModule.alias}'. Please choose a different alias.`
|
||||
)
|
||||
}
|
||||
|
||||
if (loadedModule.main) {
|
||||
if (modules.some((m) => m.main)) {
|
||||
throw new Error(`Module ${moduleKey} already have a 'main' registered.`)
|
||||
}
|
||||
}
|
||||
|
||||
modules.push(loadedModule)
|
||||
MedusaModule.modules_.set(moduleKey, modules!)
|
||||
}
|
||||
|
||||
public static async bootstrap<T>(
|
||||
moduleKey: string,
|
||||
defaultPath: string,
|
||||
declaration?: InternalModuleDeclaration | ExternalModuleDeclaration,
|
||||
moduleExports?: ModuleExports,
|
||||
injectedDependencies?: Record<string, any>
|
||||
): Promise<{
|
||||
[key: string]: any
|
||||
[key: string]: T
|
||||
}> {
|
||||
const hashKey = simpleHash(
|
||||
stringifyCircular({ moduleKey, defaultPath, declaration })
|
||||
@@ -43,13 +133,32 @@ export class MedusaModule {
|
||||
return MedusaModule.instances_.get(hashKey)
|
||||
}
|
||||
|
||||
let modDeclaration = declaration
|
||||
if (MedusaModule.loading_.has(hashKey)) {
|
||||
return MedusaModule.loading_.get(hashKey)
|
||||
}
|
||||
|
||||
let finishLoading: any
|
||||
let errorLoading: any
|
||||
MedusaModule.loading_.set(
|
||||
hashKey,
|
||||
new Promise((resolve, reject) => {
|
||||
finishLoading = resolve
|
||||
errorLoading = reject
|
||||
})
|
||||
)
|
||||
|
||||
let modDeclaration =
|
||||
declaration ??
|
||||
({} as InternalModuleDeclaration | ExternalModuleDeclaration)
|
||||
|
||||
if (declaration?.scope !== MODULE_SCOPE.EXTERNAL) {
|
||||
modDeclaration = {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.ISOLATED,
|
||||
resolve: defaultPath,
|
||||
options: declaration,
|
||||
alias: declaration?.alias,
|
||||
main: declaration?.main,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,22 +176,45 @@ export class MedusaModule {
|
||||
moduleExports
|
||||
)
|
||||
|
||||
await moduleLoader({
|
||||
container,
|
||||
moduleResolutions,
|
||||
logger,
|
||||
})
|
||||
try {
|
||||
await moduleLoader({
|
||||
container,
|
||||
moduleResolutions,
|
||||
logger,
|
||||
})
|
||||
} catch (err) {
|
||||
errorLoading(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
const services = {}
|
||||
|
||||
for (const resolution of Object.values(moduleResolutions)) {
|
||||
for (const resolution of Object.values(
|
||||
moduleResolutions
|
||||
) as ModuleResolution[]) {
|
||||
const keyName = resolution.definition.key
|
||||
const registrationName = resolution.definition.registrationName
|
||||
|
||||
services[keyName] = container.resolve(registrationName)
|
||||
services[keyName].__definition = resolution.definition
|
||||
|
||||
if (resolution.definition.isQueryable) {
|
||||
services[keyName].__joinerConfig = await services[
|
||||
keyName
|
||||
].__joinerConfig()
|
||||
}
|
||||
|
||||
MedusaModule.registerModule(keyName, {
|
||||
key: keyName,
|
||||
hash: hashKey,
|
||||
alias: modDeclaration.alias ?? hashKey,
|
||||
main: !!modDeclaration.main,
|
||||
})
|
||||
}
|
||||
|
||||
MedusaModule.instances_.set(hashKey, services)
|
||||
finishLoading(services)
|
||||
MedusaModule.loading_.delete(hashKey)
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
208
packages/modules-sdk/src/remote-query.ts
Normal file
208
packages/modules-sdk/src/remote-query.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import { RemoteJoiner } from "@medusajs/orchestration"
|
||||
import {
|
||||
JoinerRelationship,
|
||||
JoinerServiceConfig,
|
||||
ModuleDefinition,
|
||||
RemoteExpandProperty,
|
||||
} from "@medusajs/types"
|
||||
import { toPascalCase } from "@medusajs/utils"
|
||||
import { MedusaModule } from "./medusa-module"
|
||||
|
||||
export class RemoteQuery {
|
||||
private remoteJoiner: RemoteJoiner
|
||||
private modulesMap: Map<string, any> = new Map()
|
||||
|
||||
constructor(
|
||||
modulesLoaded?: (any & {
|
||||
__joinerConfig: JoinerServiceConfig
|
||||
__definition: ModuleDefinition
|
||||
})[],
|
||||
remoteFetchData?: (
|
||||
expand: RemoteExpandProperty,
|
||||
keyField: string,
|
||||
ids?: (unknown | unknown[])[],
|
||||
relationship?: JoinerRelationship
|
||||
) => Promise<{
|
||||
data: unknown[] | { [path: string]: unknown[] }
|
||||
path?: string
|
||||
}>
|
||||
) {
|
||||
if (!modulesLoaded?.length) {
|
||||
modulesLoaded = [...MedusaModule.getLoadedModules().entries()].map(
|
||||
([, mod]) => mod
|
||||
)
|
||||
}
|
||||
|
||||
const servicesConfig: JoinerServiceConfig[] = []
|
||||
for (const modService of modulesLoaded) {
|
||||
const mod: any = Object.values(modService)[0]
|
||||
|
||||
if (!mod.__definition.isQueryable) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (this.modulesMap.has(mod.__definition.key)) {
|
||||
throw new Error(
|
||||
`Duplicated instance of module ${mod.__definition.key} is not allowed.`
|
||||
)
|
||||
}
|
||||
|
||||
this.modulesMap.set(mod.__definition.key, mod)
|
||||
servicesConfig.push(mod.__joinerConfig)
|
||||
}
|
||||
|
||||
this.remoteJoiner = new RemoteJoiner(
|
||||
servicesConfig,
|
||||
remoteFetchData ?? this.remoteFetchData.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
public setFetchDataCallback(
|
||||
remoteFetchData: (
|
||||
expand: RemoteExpandProperty,
|
||||
keyField: string,
|
||||
ids?: (unknown | unknown[])[],
|
||||
relationship?: any
|
||||
) => Promise<{
|
||||
data: unknown[] | { [path: string]: unknown[] }
|
||||
path?: string
|
||||
}>
|
||||
): void {
|
||||
this.remoteJoiner.setFetchDataCallback(remoteFetchData)
|
||||
}
|
||||
|
||||
private static getAllFieldsAndRelations(
|
||||
data: any,
|
||||
prefix = ""
|
||||
): { select: string[]; relations: string[] } {
|
||||
let fields: Set<string> = new Set()
|
||||
let relations: string[] = []
|
||||
|
||||
data.fields?.forEach((field: string) => {
|
||||
fields.add(prefix ? `${prefix}.${field}` : field)
|
||||
})
|
||||
|
||||
if (data.expands) {
|
||||
for (const property in data.expands) {
|
||||
const newPrefix = prefix ? `${prefix}.${property}` : property
|
||||
|
||||
relations.push(newPrefix)
|
||||
fields.delete(newPrefix)
|
||||
|
||||
const result = RemoteQuery.getAllFieldsAndRelations(
|
||||
data.expands[property],
|
||||
newPrefix
|
||||
)
|
||||
|
||||
result.select.forEach(fields.add, fields)
|
||||
relations = relations.concat(result.relations)
|
||||
}
|
||||
}
|
||||
|
||||
return { select: [...fields], relations }
|
||||
}
|
||||
|
||||
private hasPagination(options: { [attr: string]: unknown }): boolean {
|
||||
if (!options) {
|
||||
return false
|
||||
}
|
||||
|
||||
const attrs = ["skip", "cursor"]
|
||||
return Object.keys(options).some((key) => attrs.includes(key))
|
||||
}
|
||||
|
||||
private buildPagination(options, count) {
|
||||
return {
|
||||
skip: options.skip,
|
||||
take: options.take,
|
||||
cursor: options.cursor,
|
||||
// TODO: next cursor
|
||||
count,
|
||||
}
|
||||
}
|
||||
|
||||
public async remoteFetchData(
|
||||
expand: RemoteExpandProperty,
|
||||
keyField: string,
|
||||
ids?: (unknown | unknown[])[],
|
||||
relationship?: JoinerRelationship
|
||||
): Promise<{
|
||||
data: unknown[] | { [path: string]: unknown }
|
||||
path?: string
|
||||
}> {
|
||||
const serviceConfig = expand.serviceConfig
|
||||
const service = this.modulesMap.get(serviceConfig.serviceName)
|
||||
|
||||
let filters = {}
|
||||
const options = {
|
||||
...RemoteQuery.getAllFieldsAndRelations(expand),
|
||||
}
|
||||
|
||||
const availableOptions = [
|
||||
"skip",
|
||||
"take",
|
||||
"limit",
|
||||
"offset",
|
||||
"cursor",
|
||||
"sort",
|
||||
]
|
||||
const availableOptionsAlias = new Map([
|
||||
["limit", "take"],
|
||||
["offset", "skip"],
|
||||
])
|
||||
|
||||
for (const arg of expand.args || []) {
|
||||
if (arg.name === "filters" && arg.value) {
|
||||
filters = { ...arg.value }
|
||||
} else if (availableOptions.includes(arg.name)) {
|
||||
const argName = availableOptionsAlias.has(arg.name)
|
||||
? availableOptionsAlias.get(arg.name)!
|
||||
: arg.name
|
||||
options[argName] = arg.value
|
||||
}
|
||||
}
|
||||
|
||||
if (ids) {
|
||||
filters[keyField] = ids
|
||||
}
|
||||
|
||||
const hasPagination = this.hasPagination(options)
|
||||
|
||||
let methodName = hasPagination ? "listAndCount" : "list"
|
||||
|
||||
if (relationship?.args?.methodSuffix) {
|
||||
methodName += toPascalCase(relationship.args.methodSuffix)
|
||||
} else if (serviceConfig?.args?.methodSuffix) {
|
||||
methodName += toPascalCase(serviceConfig.args.methodSuffix)
|
||||
}
|
||||
|
||||
if (typeof service[methodName] !== "function") {
|
||||
throw new Error(
|
||||
`Method "${methodName}" does not exist on "${serviceConfig.serviceName}"`
|
||||
)
|
||||
}
|
||||
|
||||
const result = await service[methodName](filters, options)
|
||||
|
||||
if (hasPagination) {
|
||||
const [data, count] = result
|
||||
return {
|
||||
data: {
|
||||
rows: data,
|
||||
metadata: this.buildPagination(options, count),
|
||||
},
|
||||
path: "rows",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
public async query(query: string, variables: any = {}): Promise<any> {
|
||||
return await this.remoteJoiner.query(
|
||||
RemoteJoiner.parseQuery(query, variables)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
"author": "Medusa",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@medusajs/types": "^1.8.10",
|
||||
"@medusajs/types": "^1.8.11",
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^25.5.4",
|
||||
"ts-jest": "^25.5.1",
|
||||
|
||||
@@ -3,22 +3,36 @@ import { remoteJoinerData } from "./../../__fixtures__/joiner/data"
|
||||
|
||||
export const serviceConfigs: JoinerServiceConfig[] = [
|
||||
{
|
||||
serviceName: "User",
|
||||
serviceName: "user",
|
||||
primaryKeys: ["id"],
|
||||
args: {
|
||||
methodSuffix: "User",
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: "me",
|
||||
args: {
|
||||
extraArgument: 123,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "customer",
|
||||
},
|
||||
],
|
||||
relationships: [
|
||||
{
|
||||
foreignKey: "products.product_id",
|
||||
serviceName: "Product",
|
||||
serviceName: "product",
|
||||
primaryKey: "id",
|
||||
alias: "product",
|
||||
},
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: "Variant",
|
||||
resolve: {
|
||||
serviceName: "variantService",
|
||||
relationship: {
|
||||
foreignKey: "user_id",
|
||||
serviceName: "User",
|
||||
serviceName: "user",
|
||||
primaryKey: "id",
|
||||
alias: "user",
|
||||
},
|
||||
@@ -26,55 +40,58 @@ export const serviceConfigs: JoinerServiceConfig[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
serviceName: "Product",
|
||||
serviceName: "product",
|
||||
primaryKeys: ["id", "sku"],
|
||||
relationships: [
|
||||
{
|
||||
foreignKey: "user_id",
|
||||
serviceName: "User",
|
||||
serviceName: "user",
|
||||
primaryKey: "id",
|
||||
alias: "user",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
serviceName: "Variant",
|
||||
serviceName: "variantService",
|
||||
alias: {
|
||||
name: "variant",
|
||||
},
|
||||
primaryKeys: ["id"],
|
||||
relationships: [
|
||||
{
|
||||
foreignKey: "product_id",
|
||||
serviceName: "Product",
|
||||
serviceName: "product",
|
||||
primaryKey: "id",
|
||||
alias: "product",
|
||||
},
|
||||
{
|
||||
foreignKey: "variant_id",
|
||||
primaryKey: "id",
|
||||
serviceName: "Order",
|
||||
serviceName: "order",
|
||||
alias: "orders",
|
||||
inverse: true, // In an inverted relationship the foreign key is on Order and the primary key is on variant
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
serviceName: "Order",
|
||||
serviceName: "order",
|
||||
primaryKeys: ["id"],
|
||||
relationships: [
|
||||
{
|
||||
foreignKey: "product_id",
|
||||
serviceName: "Product",
|
||||
serviceName: "product",
|
||||
primaryKey: "id",
|
||||
alias: "product",
|
||||
},
|
||||
{
|
||||
foreignKey: "products.variant_id,product_id",
|
||||
serviceName: "Variant",
|
||||
serviceName: "variantService",
|
||||
primaryKey: "id,product_id",
|
||||
alias: "variant",
|
||||
},
|
||||
{
|
||||
foreignKey: "user_id",
|
||||
serviceName: "User",
|
||||
serviceName: "user",
|
||||
primaryKey: "id",
|
||||
alias: "user",
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ describe("RemoteJoiner.parseQuery", () => {
|
||||
const rjQuery = parser.parseQuery()
|
||||
|
||||
expect(rjQuery).toEqual({
|
||||
service: "order",
|
||||
alias: "order",
|
||||
fields: ["id", "number", "date"],
|
||||
expands: [],
|
||||
})
|
||||
@@ -50,7 +50,7 @@ describe("RemoteJoiner.parseQuery", () => {
|
||||
const rjQuery = parser.parseQuery()
|
||||
|
||||
expect(rjQuery).toEqual({
|
||||
service: "order",
|
||||
alias: "order",
|
||||
fields: ["id", "number", "date"],
|
||||
expands: [],
|
||||
args: [
|
||||
@@ -77,6 +77,44 @@ describe("RemoteJoiner.parseQuery", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("Simple query with mapping fields to services", async () => {
|
||||
const graphqlQuery = `
|
||||
query {
|
||||
order {
|
||||
id
|
||||
number
|
||||
date
|
||||
products {
|
||||
product_id
|
||||
variant_id
|
||||
order
|
||||
variant {
|
||||
name
|
||||
sku
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const parser = new GraphQLParser(graphqlQuery, {})
|
||||
const rjQuery = parser.parseQuery()
|
||||
|
||||
expect(rjQuery).toEqual({
|
||||
alias: "order",
|
||||
fields: ["id", "number", "date", "products"],
|
||||
expands: [
|
||||
{
|
||||
property: "products",
|
||||
fields: ["product_id", "variant_id", "order", "variant"],
|
||||
},
|
||||
{
|
||||
property: "products.variant",
|
||||
fields: ["name", "sku"],
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("Nested query with fields", async () => {
|
||||
const graphqlQuery = `
|
||||
query {
|
||||
@@ -100,7 +138,7 @@ describe("RemoteJoiner.parseQuery", () => {
|
||||
const rjQuery = parser.parseQuery()
|
||||
|
||||
expect(rjQuery).toEqual({
|
||||
service: "order",
|
||||
alias: "order",
|
||||
fields: ["id", "number", "date", "products"],
|
||||
expands: [
|
||||
{
|
||||
@@ -138,7 +176,7 @@ describe("RemoteJoiner.parseQuery", () => {
|
||||
const rjQuery = parser.parseQuery()
|
||||
|
||||
expect(rjQuery).toEqual({
|
||||
service: "order",
|
||||
alias: "order",
|
||||
fields: ["id", "number", "date", "products"],
|
||||
expands: [
|
||||
{
|
||||
@@ -205,7 +243,7 @@ describe("RemoteJoiner.parseQuery", () => {
|
||||
const rjQuery = parser.parseQuery()
|
||||
|
||||
expect(rjQuery).toEqual({
|
||||
service: "order",
|
||||
alias: "order",
|
||||
fields: ["id", "number", "date", "products"],
|
||||
expands: [
|
||||
{
|
||||
|
||||
@@ -45,8 +45,9 @@ const fetchServiceDataCallback = async (
|
||||
relationship?: any
|
||||
) => {
|
||||
const serviceConfig = expand.serviceConfig
|
||||
const moduleRegistryName =
|
||||
lowerCaseFirst(serviceConfig.serviceName) + "Service"
|
||||
const moduleRegistryName = !serviceConfig.serviceName.endsWith("Service")
|
||||
? lowerCaseFirst(serviceConfig.serviceName) + "Service"
|
||||
: serviceConfig.serviceName
|
||||
|
||||
const service = container.resolve(moduleRegistryName)
|
||||
const methodName = relationship?.inverse
|
||||
@@ -74,7 +75,7 @@ describe("RemoteJoiner", () => {
|
||||
|
||||
it("Simple query of a service, its id and no fields specified", async () => {
|
||||
const query = {
|
||||
service: "User",
|
||||
service: "user",
|
||||
args: [
|
||||
{
|
||||
name: "id",
|
||||
@@ -143,7 +144,7 @@ describe("RemoteJoiner", () => {
|
||||
|
||||
it("Query of a service, expanding a property and restricting the fields expanded", async () => {
|
||||
const query = {
|
||||
service: "User",
|
||||
service: "user",
|
||||
args: [
|
||||
{
|
||||
name: "id",
|
||||
@@ -215,7 +216,7 @@ describe("RemoteJoiner", () => {
|
||||
|
||||
it("Query a service expanding multiple nested properties", async () => {
|
||||
const query = {
|
||||
service: "Order",
|
||||
service: "order",
|
||||
fields: ["number", "date", "products"],
|
||||
expands: [
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ describe("RemoteJoiner", () => {
|
||||
|
||||
it("Simple query of a service, its id and no fields specified", async () => {
|
||||
const query = {
|
||||
service: "User",
|
||||
service: "user",
|
||||
args: [
|
||||
{
|
||||
name: "id",
|
||||
@@ -69,20 +69,62 @@ describe("RemoteJoiner", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("Transforms main service name into PascalCase", async () => {
|
||||
it("Simple query of a service by its alias", async () => {
|
||||
const query = {
|
||||
service: "user",
|
||||
alias: "customer",
|
||||
fields: ["id"],
|
||||
args: [
|
||||
{
|
||||
name: "id",
|
||||
value: "1",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
await joiner.query(query)
|
||||
|
||||
expect(serviceMock.userService).toHaveBeenCalledTimes(1)
|
||||
expect(serviceMock.userService).toHaveBeenCalledWith({
|
||||
args: [],
|
||||
fields: ["id"],
|
||||
options: { id: ["1"] },
|
||||
})
|
||||
})
|
||||
|
||||
it("Simple query of a service by its alias with extra arguments", async () => {
|
||||
const query = {
|
||||
alias: "me",
|
||||
fields: ["id"],
|
||||
args: [
|
||||
{
|
||||
name: "id",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
name: "arg1",
|
||||
value: "abc",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
await joiner.query(query)
|
||||
|
||||
expect(serviceMock.userService).toHaveBeenCalledTimes(1)
|
||||
expect(serviceMock.userService).toHaveBeenCalledWith({
|
||||
args: [
|
||||
{
|
||||
name: "arg1",
|
||||
value: "abc",
|
||||
},
|
||||
],
|
||||
fields: ["id"],
|
||||
options: { id: [1] },
|
||||
})
|
||||
})
|
||||
|
||||
it("Simple query of a service, its id and a few fields specified", async () => {
|
||||
const query = {
|
||||
service: "User",
|
||||
service: "user",
|
||||
args: [
|
||||
{
|
||||
name: "id",
|
||||
@@ -148,7 +190,7 @@ describe("RemoteJoiner", () => {
|
||||
|
||||
it("Query a service using more than 1 argument, expanding a property with another argument", async () => {
|
||||
const query = {
|
||||
service: "User",
|
||||
service: "user",
|
||||
args: [
|
||||
{
|
||||
name: "id",
|
||||
@@ -213,7 +255,7 @@ describe("RemoteJoiner", () => {
|
||||
|
||||
it("Query a service expanding multiple nested properties", async () => {
|
||||
const query = {
|
||||
service: "Order",
|
||||
service: "order",
|
||||
fields: ["number", "date", "products"],
|
||||
expands: [
|
||||
{
|
||||
|
||||
@@ -86,28 +86,29 @@ class GraphQLParser {
|
||||
if (selection.kind === "Field") {
|
||||
const fieldNode = selection as FieldNode
|
||||
|
||||
if (fieldNode.selectionSet) {
|
||||
const entityName = parentName
|
||||
? `${parentName}.${fieldNode.name.value}`
|
||||
: fieldNode.name.value
|
||||
|
||||
const nestedEntity: Entity = {
|
||||
property: entityName.replace(`${mainService}.`, ""),
|
||||
fields: fieldNode.selectionSet.selections.map(
|
||||
(field) => (field as FieldNode).name.value
|
||||
),
|
||||
args: this.parseArguments(fieldNode.arguments!),
|
||||
}
|
||||
|
||||
entities.push(nestedEntity)
|
||||
entities.push(
|
||||
...this.extractEntities(
|
||||
fieldNode.selectionSet,
|
||||
entityName,
|
||||
mainService
|
||||
)
|
||||
)
|
||||
if (!fieldNode.selectionSet) {
|
||||
return
|
||||
}
|
||||
|
||||
const propName = fieldNode.name.value
|
||||
const entityName = parentName ? `${parentName}.${propName}` : propName
|
||||
|
||||
const nestedEntity: Entity = {
|
||||
property: entityName.replace(`${mainService}.`, ""),
|
||||
fields: fieldNode.selectionSet.selections.map(
|
||||
(field) => (field as FieldNode).name.value
|
||||
),
|
||||
args: this.parseArguments(fieldNode.arguments!),
|
||||
}
|
||||
|
||||
entities.push(nestedEntity)
|
||||
entities.push(
|
||||
...this.extractEntities(
|
||||
fieldNode.selectionSet,
|
||||
entityName,
|
||||
mainService
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -126,8 +127,9 @@ class GraphQLParser {
|
||||
const rootFieldNode = queryDefinition.selectionSet
|
||||
.selections[0] as FieldNode
|
||||
|
||||
const propName = rootFieldNode.name.value
|
||||
const remoteJoinConfig: RemoteJoinerQuery = {
|
||||
service: rootFieldNode.name.value,
|
||||
alias: propName,
|
||||
fields: [],
|
||||
expands: [],
|
||||
}
|
||||
@@ -140,10 +142,11 @@ class GraphQLParser {
|
||||
remoteJoinConfig.fields = rootFieldNode.selectionSet.selections.map(
|
||||
(field) => (field as FieldNode).name.value
|
||||
)
|
||||
|
||||
remoteJoinConfig.expands = this.extractEntities(
|
||||
rootFieldNode.selectionSet,
|
||||
rootFieldNode.name.value,
|
||||
rootFieldNode.name.value
|
||||
propName,
|
||||
propName
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {
|
||||
JoinerRelationship,
|
||||
JoinerServiceConfig,
|
||||
JoinerServiceConfigAlias,
|
||||
RemoteExpandProperty,
|
||||
RemoteJoinerQuery,
|
||||
RemoteNestedExpands,
|
||||
} from "@medusajs/types"
|
||||
import { isDefined, toPascalCase } from "@medusajs/utils"
|
||||
import { isDefined } from "@medusajs/utils"
|
||||
import GraphQLParser from "./graphql-ast"
|
||||
|
||||
const BASE_PATH = "_root"
|
||||
export class RemoteJoiner {
|
||||
private serviceConfigs: JoinerServiceConfig[]
|
||||
private serviceConfigCache: Map<string, JoinerServiceConfig> = new Map()
|
||||
|
||||
private static filterFields(
|
||||
@@ -84,37 +84,77 @@ export class RemoteJoiner {
|
||||
}
|
||||
|
||||
constructor(
|
||||
serviceConfigs: JoinerServiceConfig[],
|
||||
private serviceConfigs: JoinerServiceConfig[],
|
||||
private remoteFetchData: (
|
||||
expand: RemoteExpandProperty,
|
||||
pkField: string,
|
||||
keyField: string,
|
||||
ids?: (unknown | unknown[])[],
|
||||
relationship?: any
|
||||
) => Promise<{
|
||||
data: unknown[] | { [path: string]: unknown[] }
|
||||
data: unknown[] | { [path: string]: unknown }
|
||||
path?: string
|
||||
}>
|
||||
) {
|
||||
this.serviceConfigs = this.buildReferences(serviceConfigs)
|
||||
}
|
||||
|
||||
public setFetchDataCallback(
|
||||
remoteFetchData: (
|
||||
expand: RemoteExpandProperty,
|
||||
keyField: string,
|
||||
ids?: (unknown | unknown[])[],
|
||||
relationship?: any
|
||||
) => Promise<{
|
||||
data: unknown[] | { [path: string]: unknown }
|
||||
path?: string
|
||||
}>
|
||||
): void {
|
||||
this.remoteFetchData = remoteFetchData
|
||||
}
|
||||
|
||||
private buildReferences(serviceConfigs: JoinerServiceConfig[]) {
|
||||
const expandedRelationships: Map<string, JoinerRelationship[]> = new Map()
|
||||
for (const service of serviceConfigs) {
|
||||
// self-reference
|
||||
const propName = service.serviceName.toLowerCase()
|
||||
if (this.serviceConfigCache.has(service.serviceName)) {
|
||||
throw new Error(`Service "${service.serviceName}" is already defined.`)
|
||||
}
|
||||
|
||||
if (!service.relationships) {
|
||||
service.relationships = []
|
||||
}
|
||||
|
||||
service.relationships?.push({
|
||||
alias: propName,
|
||||
foreignKey: propName + "_id",
|
||||
primaryKey: "id",
|
||||
serviceName: service.serviceName,
|
||||
})
|
||||
// add aliases
|
||||
if (!service.alias) {
|
||||
service.alias = [{ name: service.serviceName.toLowerCase() }]
|
||||
} else if (!Array.isArray(service.alias)) {
|
||||
service.alias = [service.alias]
|
||||
}
|
||||
|
||||
this.serviceConfigCache.set(service.serviceName, service)
|
||||
// self-reference
|
||||
for (const alias of service.alias) {
|
||||
if (this.serviceConfigCache.has(`alias_${alias.name}}`)) {
|
||||
const defined = this.serviceConfigCache.get(`alias_${alias.name}}`)
|
||||
throw new Error(
|
||||
`Cannot add alias "${alias.name}" for "${service.serviceName}". It is already defined for Service "${defined?.serviceName}".`
|
||||
)
|
||||
}
|
||||
|
||||
const args =
|
||||
service.args || alias.args
|
||||
? { ...service.args, ...alias.args }
|
||||
: undefined
|
||||
|
||||
service.relationships?.push({
|
||||
alias: alias.name,
|
||||
foreignKey: alias.name + "_id",
|
||||
primaryKey: "id",
|
||||
serviceName: service.serviceName,
|
||||
args,
|
||||
})
|
||||
this.cacheServiceConfig(serviceConfigs, undefined, alias.name)
|
||||
}
|
||||
|
||||
this.cacheServiceConfig(serviceConfigs, service.serviceName)
|
||||
|
||||
if (!service.extends) {
|
||||
continue
|
||||
@@ -125,13 +165,13 @@ export class RemoteJoiner {
|
||||
expandedRelationships.set(extend.serviceName, [])
|
||||
}
|
||||
|
||||
expandedRelationships.get(extend.serviceName)!.push(extend.resolve)
|
||||
expandedRelationships.get(extend.serviceName)!.push(extend.relationship)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [serviceName, relationships] of expandedRelationships) {
|
||||
if (!this.serviceConfigCache.has(serviceName)) {
|
||||
throw new Error(`Service ${serviceName} not found`)
|
||||
throw new Error(`Service "${serviceName}" was not found`)
|
||||
}
|
||||
|
||||
const service = this.serviceConfigCache.get(serviceName)
|
||||
@@ -141,16 +181,49 @@ export class RemoteJoiner {
|
||||
return serviceConfigs
|
||||
}
|
||||
|
||||
private findServiceConfig(
|
||||
serviceName: string
|
||||
private getServiceConfig(
|
||||
serviceName?: string,
|
||||
serviceAlias?: string
|
||||
): JoinerServiceConfig | undefined {
|
||||
if (!this.serviceConfigCache.has(serviceName)) {
|
||||
const config = this.serviceConfigs.find(
|
||||
(config) => config.serviceName === serviceName
|
||||
)
|
||||
this.serviceConfigCache.set(serviceName, config!)
|
||||
if (serviceAlias) {
|
||||
const name = `alias_${serviceAlias}`
|
||||
return this.serviceConfigCache.get(name)
|
||||
}
|
||||
return this.serviceConfigCache.get(serviceName)
|
||||
|
||||
return this.serviceConfigCache.get(serviceName!)
|
||||
}
|
||||
|
||||
private cacheServiceConfig(
|
||||
serviceConfigs,
|
||||
serviceName?: string,
|
||||
serviceAlias?: string
|
||||
): void {
|
||||
if (serviceAlias) {
|
||||
const name = `alias_${serviceAlias}`
|
||||
if (!this.serviceConfigCache.has(name)) {
|
||||
let aliasConfig: JoinerServiceConfigAlias | undefined
|
||||
const config = serviceConfigs.find((conf) => {
|
||||
const aliases = conf.alias as JoinerServiceConfigAlias[]
|
||||
const hasArgs = aliases?.find((alias) => alias.name === serviceAlias)
|
||||
aliasConfig = hasArgs
|
||||
return hasArgs
|
||||
})
|
||||
|
||||
if (config) {
|
||||
const serviceConfig = { ...config }
|
||||
if (aliasConfig) {
|
||||
serviceConfig.args = { ...config?.args, ...aliasConfig?.args }
|
||||
}
|
||||
this.serviceConfigCache.set(name, serviceConfig)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const config = serviceConfigs.find(
|
||||
(config) => config.serviceName === serviceName
|
||||
)
|
||||
this.serviceConfigCache.set(serviceName!, config)
|
||||
}
|
||||
|
||||
private async fetchData(
|
||||
@@ -159,7 +232,7 @@ export class RemoteJoiner {
|
||||
ids?: (unknown | unknown[])[],
|
||||
relationship?: any
|
||||
): Promise<{
|
||||
data: unknown[] | { [path: string]: unknown[] }
|
||||
data: unknown[] | { [path: string]: unknown }
|
||||
path?: string
|
||||
}> {
|
||||
let uniqueIds = Array.isArray(ids) ? ids : ids ? [ids] : undefined
|
||||
@@ -218,7 +291,7 @@ export class RemoteJoiner {
|
||||
|
||||
const stack: [
|
||||
any[],
|
||||
RemoteJoinerQuery,
|
||||
Partial<RemoteJoinerQuery>,
|
||||
Map<string, RemoteExpandProperty>,
|
||||
string,
|
||||
Set<string>
|
||||
@@ -245,23 +318,25 @@ export class RemoteJoiner {
|
||||
resolvedPaths.add(expandedPath)
|
||||
|
||||
const property = expand.property || ""
|
||||
const parentServiceConfig = this.findServiceConfig(currentQuery.service)
|
||||
const parentServiceConfig = this.getServiceConfig(
|
||||
currentQuery.service,
|
||||
currentQuery.alias
|
||||
)
|
||||
|
||||
await this.expandProperty(currentItems, parentServiceConfig!, expand)
|
||||
|
||||
const relationship = parentServiceConfig?.relationships?.find(
|
||||
(relation) => relation.alias === property
|
||||
)
|
||||
|
||||
const nestedItems = RemoteJoiner.getNestedItems(currentItems, property)
|
||||
|
||||
if (nestedItems.length > 0) {
|
||||
const nextProp = relationship
|
||||
? {
|
||||
...currentQuery,
|
||||
service: relationship.serviceName,
|
||||
}
|
||||
: currentQuery
|
||||
const relationship = expand.serviceConfig
|
||||
|
||||
let nextProp = currentQuery
|
||||
if (relationship) {
|
||||
const relQuery = {
|
||||
service: relationship.serviceName,
|
||||
}
|
||||
nextProp = relQuery
|
||||
}
|
||||
|
||||
stack.push([
|
||||
nestedItems,
|
||||
@@ -356,9 +431,19 @@ export class RemoteJoiner {
|
||||
|
||||
if (Array.isArray(item[field])) {
|
||||
item[relationship.alias] = item[field]
|
||||
.map((id) => relatedDataMap[id])
|
||||
.map((id) => {
|
||||
if (relationship.isList && !Array.isArray(relatedDataMap[id])) {
|
||||
relatedDataMap[id] = [relatedDataMap[id]]
|
||||
}
|
||||
|
||||
return relatedDataMap[id]
|
||||
})
|
||||
.filter((relatedItem) => relatedItem !== undefined)
|
||||
} else {
|
||||
if (relationship.isList && !Array.isArray(relatedDataMap[itemKey])) {
|
||||
relatedDataMap[itemKey] = [relatedDataMap[itemKey]]
|
||||
}
|
||||
|
||||
item[relationship.alias] = relatedDataMap[itemKey]
|
||||
}
|
||||
}
|
||||
@@ -439,9 +524,7 @@ export class RemoteJoiner {
|
||||
}
|
||||
}
|
||||
|
||||
currentServiceConfig = this.findServiceConfig(
|
||||
relationship.serviceName
|
||||
)
|
||||
currentServiceConfig = this.getServiceConfig(relationship.serviceName)
|
||||
|
||||
if (!currentServiceConfig) {
|
||||
throw new Error(
|
||||
@@ -522,11 +605,17 @@ export class RemoteJoiner {
|
||||
}
|
||||
|
||||
async query(queryObj: RemoteJoinerQuery): Promise<any> {
|
||||
queryObj.service = toPascalCase(queryObj.service)
|
||||
const serviceConfig = this.findServiceConfig(queryObj.service)
|
||||
const serviceConfig = this.getServiceConfig(
|
||||
queryObj.service,
|
||||
queryObj.alias
|
||||
)
|
||||
|
||||
if (!serviceConfig) {
|
||||
throw new Error(`Service not found: ${queryObj.service}`)
|
||||
if (queryObj.alias) {
|
||||
throw new Error(`Service with alias "${queryObj.alias}" was not found.`)
|
||||
}
|
||||
|
||||
throw new Error(`Service "${queryObj.service}" was not found.`)
|
||||
}
|
||||
|
||||
let pkName = serviceConfig.primaryKeys[0]
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { ProductCategoryService } from "@services"
|
||||
import { ProductCategoryRepository } from "@repositories"
|
||||
import { ProductCategory } from "@models"
|
||||
import { ProductCategoryRepository } from "@repositories"
|
||||
import { ProductCategoryService } from "@services"
|
||||
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { productCategoriesData } from "../../../__fixtures__/product-category/data"
|
||||
import { TestDatabase } from "../../../utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { ProductCollection } from "@models"
|
||||
import { ProductCollectionService } from "@services"
|
||||
import { ProductCollectionRepository } from "@repositories"
|
||||
import { ProductCollectionService } from "@services"
|
||||
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { createCollections } from "../../../__fixtures__/product"
|
||||
import { TestDatabase } from "../../../utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { ProductTagService } from "@services"
|
||||
import { ProductTagRepository } from "@repositories"
|
||||
import { Product } from "@models"
|
||||
import { ProductTagRepository } from "@repositories"
|
||||
import { ProductTagService } from "@services"
|
||||
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { createProductAndTags } from "../../../__fixtures__/product"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { createProductAndTags } from "../../../__fixtures__/product"
|
||||
import { TestDatabase } from "../../../utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
|
||||
@@ -280,18 +280,15 @@ describe("ProductVariant Service", () => {
|
||||
expect.objectContaining({
|
||||
id: productVariantTestOne,
|
||||
title: "variant 1",
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should return requested attributes when requested through config", async () => {
|
||||
const result = await service.retrieve(
|
||||
variantOne.id,
|
||||
{
|
||||
select: ["id", "title", "product.title"] as any,
|
||||
relations: ["product"],
|
||||
}
|
||||
)
|
||||
const result = await service.retrieve(variantOne.id, {
|
||||
select: ["id", "title", "product.title"] as any,
|
||||
relations: ["product"],
|
||||
})
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -302,7 +299,7 @@ describe("ProductVariant Service", () => {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
}),
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -315,7 +312,9 @@ describe("ProductVariant Service", () => {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual("ProductVariant with id: does-not-exist was not found")
|
||||
expect(error.message).toEqual(
|
||||
"ProductVariant with id: does-not-exist was not found"
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when an id is not provided", async () => {
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { ProductService } from "@services"
|
||||
import { ProductRepository } from "@repositories"
|
||||
import { Image, Product, ProductCategory, ProductVariant } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductDTO } from "@medusajs/types"
|
||||
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import {
|
||||
assignCategoriesToProduct,
|
||||
createImages,
|
||||
@@ -17,7 +10,14 @@ import {
|
||||
productsData,
|
||||
variantsData,
|
||||
} from "../../../__fixtures__/product/data"
|
||||
|
||||
import { ProductDTO } from "@medusajs/types"
|
||||
import { ProductRepository } from "@repositories"
|
||||
import { ProductService } from "@services"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { buildProductOnlyData } from "../../../__fixtures__/product/data/create-product"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { kebabCase } from "@medusajs/utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
6
packages/product/integration-tests/utils/config.ts
Normal file
6
packages/product/integration-tests/utils/config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ProductServiceInitializeOptions } from "../../src/types"
|
||||
|
||||
export const databaseOptions: ProductServiceInitializeOptions["database"] = {
|
||||
schema: "public",
|
||||
clientUrl: "medusa-products-test",
|
||||
}
|
||||
@@ -37,7 +37,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mikro-orm/cli": "5.7.12",
|
||||
"@mikro-orm/migrations": "5.7.12",
|
||||
"cross-env": "^5.2.1",
|
||||
"faker": "^6.6.6",
|
||||
"jest": "^25.5.4",
|
||||
|
||||
@@ -18,7 +18,7 @@ export const initialize = async (
|
||||
): Promise<IProductModuleService> => {
|
||||
const serviceKey = Modules.PRODUCT
|
||||
|
||||
const loaded = await MedusaModule.bootstrap(
|
||||
const loaded = await MedusaModule.bootstrap<IProductModuleService>(
|
||||
serviceKey,
|
||||
MODULE_PACKAGE_NAMES[Modules.PRODUCT],
|
||||
options as InternalModuleDeclaration | ExternalModuleDeclaration,
|
||||
@@ -26,5 +26,5 @@ export const initialize = async (
|
||||
injectedDependencies
|
||||
)
|
||||
|
||||
return loaded[serviceKey] as IProductModuleService
|
||||
return loaded[serviceKey]
|
||||
}
|
||||
|
||||
99
packages/product/src/joiner-config.ts
Normal file
99
packages/product/src/joiner-config.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { JoinerServiceConfig } from "@medusajs/types"
|
||||
|
||||
export const joinerConfig: JoinerServiceConfig = {
|
||||
serviceName: Modules.PRODUCT,
|
||||
primaryKeys: ["id", "handle"],
|
||||
alias: [
|
||||
{
|
||||
name: "product",
|
||||
},
|
||||
{
|
||||
name: "products",
|
||||
},
|
||||
{
|
||||
name: "variant",
|
||||
args: {
|
||||
methodSuffix: "Variants",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variants",
|
||||
args: {
|
||||
methodSuffix: "Variants",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_option",
|
||||
args: {
|
||||
methodSuffix: "Options",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_options",
|
||||
args: {
|
||||
methodSuffix: "Options",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_type",
|
||||
args: {
|
||||
methodSuffix: "Types",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_types",
|
||||
args: {
|
||||
methodSuffix: "Types",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_image",
|
||||
args: {
|
||||
methodSuffix: "Images",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_images",
|
||||
args: {
|
||||
methodSuffix: "Images",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_tag",
|
||||
args: {
|
||||
methodSuffix: "Tags",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_tags",
|
||||
args: {
|
||||
methodSuffix: "Tags",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_collection",
|
||||
args: {
|
||||
methodSuffix: "Collections",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_collections",
|
||||
args: {
|
||||
methodSuffix: "Collections",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_category",
|
||||
args: {
|
||||
methodSuffix: "Categories",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "product_categories",
|
||||
args: {
|
||||
methodSuffix: "Categories",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -54,7 +54,6 @@ async function loadDefault({ database, container }) {
|
||||
}
|
||||
|
||||
const entities = Object.values(ProductModels) as unknown as EntitySchema[]
|
||||
|
||||
const orm = await createConnection(database, entities)
|
||||
|
||||
container.register({
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
import { LoaderOptions } from "@medusajs/modules-sdk"
|
||||
|
||||
import { asClass } from "awilix"
|
||||
import {
|
||||
ProductCategoryService,
|
||||
ProductCollectionService,
|
||||
ProductImageService,
|
||||
ProductModuleService,
|
||||
ProductOptionService,
|
||||
ProductService,
|
||||
ProductTagService,
|
||||
ProductTypeService,
|
||||
ProductVariantService,
|
||||
} from "@services"
|
||||
import * as DefaultRepositories from "@repositories"
|
||||
|
||||
import {
|
||||
BaseRepository,
|
||||
ProductCategoryRepository,
|
||||
@@ -25,6 +12,20 @@ import {
|
||||
ProductVariantRepository,
|
||||
} from "@repositories"
|
||||
import { Constructor, DAL, ModulesSdkTypes } from "@medusajs/types"
|
||||
import {
|
||||
ProductCategoryService,
|
||||
ProductCollectionService,
|
||||
ProductImageService,
|
||||
ProductModuleService,
|
||||
ProductOptionService,
|
||||
ProductService,
|
||||
ProductTagService,
|
||||
ProductTypeService,
|
||||
ProductVariantService,
|
||||
} from "@services"
|
||||
|
||||
import { LoaderOptions } from "@medusajs/modules-sdk"
|
||||
import { asClass } from "awilix"
|
||||
import { lowerCaseFirst } from "@medusajs/utils"
|
||||
|
||||
export default async ({
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"namespaces": [
|
||||
"public"
|
||||
],
|
||||
"namespaces": ["public"],
|
||||
"name": "public",
|
||||
"tables": [
|
||||
{
|
||||
@@ -117,27 +115,21 @@
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_product_category_path",
|
||||
"columnNames": [
|
||||
"mpath"
|
||||
],
|
||||
"columnNames": ["mpath"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_product_category_handle",
|
||||
"columnNames": [
|
||||
"handle"
|
||||
],
|
||||
"columnNames": ["handle"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "product_category_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -147,13 +139,9 @@
|
||||
"foreignKeys": {
|
||||
"product_category_parent_category_id_foreign": {
|
||||
"constraintName": "product_category_parent_category_id_foreign",
|
||||
"columnNames": [
|
||||
"parent_category_id"
|
||||
],
|
||||
"columnNames": ["parent_category_id"],
|
||||
"localTableName": "public.product_category",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product_category",
|
||||
"deleteRule": "set null",
|
||||
"updateRule": "cascade"
|
||||
@@ -213,9 +201,7 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_collection_deleted_at",
|
||||
"primary": false,
|
||||
@@ -223,18 +209,14 @@
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_product_collection_handle_unique",
|
||||
"columnNames": [
|
||||
"handle"
|
||||
],
|
||||
"columnNames": ["handle"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "product_collection_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -287,18 +269,14 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"url"
|
||||
],
|
||||
"columnNames": ["url"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_image_url",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_image_deleted_at",
|
||||
"primary": false,
|
||||
@@ -306,9 +284,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "image_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -361,9 +337,7 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_tag_deleted_at",
|
||||
"primary": false,
|
||||
@@ -371,9 +345,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "product_tag_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -426,9 +398,7 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_type_deleted_at",
|
||||
"primary": false,
|
||||
@@ -436,9 +406,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "product_type_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -511,12 +479,7 @@
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"enumItems": [
|
||||
"draft",
|
||||
"proposed",
|
||||
"published",
|
||||
"rejected"
|
||||
],
|
||||
"enumItems": ["draft", "proposed", "published", "rejected"],
|
||||
"mappedType": "enum"
|
||||
},
|
||||
"thumbnail": {
|
||||
@@ -681,18 +644,14 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"type_id"
|
||||
],
|
||||
"columnNames": ["type_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_type_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_deleted_at",
|
||||
"primary": false,
|
||||
@@ -700,18 +659,14 @@
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_product_handle_unique",
|
||||
"columnNames": [
|
||||
"handle"
|
||||
],
|
||||
"columnNames": ["handle"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "product_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -721,26 +676,18 @@
|
||||
"foreignKeys": {
|
||||
"product_collection_id_foreign": {
|
||||
"constraintName": "product_collection_id_foreign",
|
||||
"columnNames": [
|
||||
"collection_id"
|
||||
],
|
||||
"columnNames": ["collection_id"],
|
||||
"localTableName": "public.product",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product_collection",
|
||||
"deleteRule": "set null",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"product_type_id_foreign": {
|
||||
"constraintName": "product_type_id_foreign",
|
||||
"columnNames": [
|
||||
"type_id"
|
||||
],
|
||||
"columnNames": ["type_id"],
|
||||
"localTableName": "public.product",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product_type",
|
||||
"deleteRule": "set null",
|
||||
"updateRule": "cascade"
|
||||
@@ -800,18 +747,14 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"product_id"
|
||||
],
|
||||
"columnNames": ["product_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_option_product_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_option_deleted_at",
|
||||
"primary": false,
|
||||
@@ -819,9 +762,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "product_option_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -831,13 +772,9 @@
|
||||
"foreignKeys": {
|
||||
"product_option_product_id_foreign": {
|
||||
"constraintName": "product_option_product_id_foreign",
|
||||
"columnNames": [
|
||||
"product_id"
|
||||
],
|
||||
"columnNames": ["product_id"],
|
||||
"localTableName": "public.product_option",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
@@ -869,10 +806,7 @@
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "product_tags_pkey",
|
||||
"columnNames": [
|
||||
"product_id",
|
||||
"product_tag_id"
|
||||
],
|
||||
"columnNames": ["product_id", "product_tag_id"],
|
||||
"composite": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -882,26 +816,18 @@
|
||||
"foreignKeys": {
|
||||
"product_tags_product_id_foreign": {
|
||||
"constraintName": "product_tags_product_id_foreign",
|
||||
"columnNames": [
|
||||
"product_id"
|
||||
],
|
||||
"columnNames": ["product_id"],
|
||||
"localTableName": "public.product_tags",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"product_tags_product_tag_id_foreign": {
|
||||
"constraintName": "product_tags_product_tag_id_foreign",
|
||||
"columnNames": [
|
||||
"product_tag_id"
|
||||
],
|
||||
"columnNames": ["product_tag_id"],
|
||||
"localTableName": "public.product_tags",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product_tag",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
@@ -934,10 +860,7 @@
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "product_images_pkey",
|
||||
"columnNames": [
|
||||
"product_id",
|
||||
"product_image_id"
|
||||
],
|
||||
"columnNames": ["product_id", "product_image_id"],
|
||||
"composite": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -947,26 +870,18 @@
|
||||
"foreignKeys": {
|
||||
"product_images_product_id_foreign": {
|
||||
"constraintName": "product_images_product_id_foreign",
|
||||
"columnNames": [
|
||||
"product_id"
|
||||
],
|
||||
"columnNames": ["product_id"],
|
||||
"localTableName": "public.product_images",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"product_images_product_image_id_foreign": {
|
||||
"constraintName": "product_images_product_image_id_foreign",
|
||||
"columnNames": [
|
||||
"product_image_id"
|
||||
],
|
||||
"columnNames": ["product_image_id"],
|
||||
"localTableName": "public.product_images",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.image",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
@@ -999,10 +914,7 @@
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "product_category_product_pkey",
|
||||
"columnNames": [
|
||||
"product_id",
|
||||
"product_category_id"
|
||||
],
|
||||
"columnNames": ["product_id", "product_category_id"],
|
||||
"composite": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -1012,26 +924,18 @@
|
||||
"foreignKeys": {
|
||||
"product_category_product_product_id_foreign": {
|
||||
"constraintName": "product_category_product_product_id_foreign",
|
||||
"columnNames": [
|
||||
"product_id"
|
||||
],
|
||||
"columnNames": ["product_id"],
|
||||
"localTableName": "public.product_category_product",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"product_category_product_product_category_id_foreign": {
|
||||
"constraintName": "product_category_product_product_category_id_foreign",
|
||||
"columnNames": [
|
||||
"product_category_id"
|
||||
],
|
||||
"columnNames": ["product_category_id"],
|
||||
"localTableName": "public.product_category_product",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product_category",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
@@ -1259,18 +1163,14 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_variant_deleted_at",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"product_id"
|
||||
],
|
||||
"columnNames": ["product_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_variant_product_id",
|
||||
"primary": false,
|
||||
@@ -1278,45 +1178,35 @@
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_product_variant_sku_unique",
|
||||
"columnNames": [
|
||||
"sku"
|
||||
],
|
||||
"columnNames": ["sku"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_product_variant_barcode_unique",
|
||||
"columnNames": [
|
||||
"barcode"
|
||||
],
|
||||
"columnNames": ["barcode"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_product_variant_ean_unique",
|
||||
"columnNames": [
|
||||
"ean"
|
||||
],
|
||||
"columnNames": ["ean"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_product_variant_upc_unique",
|
||||
"columnNames": [
|
||||
"upc"
|
||||
],
|
||||
"columnNames": ["upc"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "product_variant_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -1326,13 +1216,9 @@
|
||||
"foreignKeys": {
|
||||
"product_variant_product_id_foreign": {
|
||||
"constraintName": "product_variant_product_id_foreign",
|
||||
"columnNames": [
|
||||
"product_id"
|
||||
],
|
||||
"columnNames": ["product_id"],
|
||||
"localTableName": "public.product_variant",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
@@ -1401,27 +1287,21 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"option_id"
|
||||
],
|
||||
"columnNames": ["option_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_option_value_option_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"variant_id"
|
||||
],
|
||||
"columnNames": ["variant_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_option_value_variant_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_product_option_value_deleted_at",
|
||||
"primary": false,
|
||||
@@ -1429,9 +1309,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "product_option_value_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -1441,25 +1319,17 @@
|
||||
"foreignKeys": {
|
||||
"product_option_value_option_id_foreign": {
|
||||
"constraintName": "product_option_value_option_id_foreign",
|
||||
"columnNames": [
|
||||
"option_id"
|
||||
],
|
||||
"columnNames": ["option_id"],
|
||||
"localTableName": "public.product_option_value",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product_option",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"product_option_value_variant_id_foreign": {
|
||||
"constraintName": "product_option_value_variant_id_foreign",
|
||||
"columnNames": [
|
||||
"variant_id"
|
||||
],
|
||||
"columnNames": ["variant_id"],
|
||||
"localTableName": "public.product_option_value",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.product_variant",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
|
||||
@@ -7,11 +7,10 @@ import {
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
import { ProductOption, ProductVariant } from "./index"
|
||||
|
||||
import ProductOption from "./product-option"
|
||||
import { ProductVariant } from "./index"
|
||||
import { SoftDeletable } from "../utils"
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
|
||||
type OptionalFields =
|
||||
| "created_at"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
import { Product } from "./index"
|
||||
import ProductOptionValue from "./product-option-value"
|
||||
import { SoftDeletable } from "../utils"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
Property,
|
||||
Unique,
|
||||
} from "@mikro-orm/core"
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
import { Product } from "@models"
|
||||
import ProductOptionValue from "./product-option-value"
|
||||
import { SoftDeletable } from "../utils"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as ProductModels from "@models"
|
||||
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
import { ProductModuleService } from "@services"
|
||||
import loadContainer from "./loaders/container"
|
||||
import loadConnection from "./loaders/connection"
|
||||
import * as ProductModels from "@models"
|
||||
import loadContainer from "./loaders/container"
|
||||
|
||||
const service = ProductModuleService
|
||||
const loaders = [loadContainer, loadConnection] as any
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { createConnection } from "../utils"
|
||||
import * as ProductModels from "@models"
|
||||
|
||||
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
|
||||
|
||||
import { EntitySchema } from "@mikro-orm/core"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { createConnection } from "../utils"
|
||||
|
||||
/**
|
||||
* This script is only valid for mikro orm managers. If a user provide a custom manager
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export { default as ProductModuleService } from "./product-module-service"
|
||||
export { default as ProductService } from "./product"
|
||||
export { default as ProductCategoryService } from "./product-category"
|
||||
export { default as ProductCollectionService } from "./product-collection"
|
||||
export { default as ProductModuleService } from "./product-module-service"
|
||||
export { default as ProductTagService } from "./product-tag"
|
||||
export { default as ProductVariantService } from "./product-variant"
|
||||
export { default as ProductCollectionService } from "./product-collection"
|
||||
export { default as ProductCategoryService } from "./product-category"
|
||||
export { default as ProductTypeService } from "./product-type"
|
||||
export { default as ProductOptionService } from "./product-option"
|
||||
export { default as ProductImageService } from "./product-image"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ProductCollection } from "@models"
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { ModulesSdkUtils, retrieveEntity } from "@medusajs/utils"
|
||||
|
||||
import { ProductCollection } from "@models"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productCollectionRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
DAL,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
JoinerServiceConfig,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import ProductImageService from "./product-image"
|
||||
@@ -34,6 +35,7 @@ import {
|
||||
MedusaContext,
|
||||
} from "@medusajs/utils"
|
||||
import { shouldForceTransaction } from "../utils"
|
||||
import { joinerConfig } from "./../joiner-config"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
@@ -96,6 +98,10 @@ export default class ProductModuleService<
|
||||
this.productOptionService_ = productOptionService
|
||||
}
|
||||
|
||||
__joinerConfig(): JoinerServiceConfig {
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductProps = {},
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
Modules,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { IEventBusService, IStockLocationService } from "@medusajs/types"
|
||||
import { StockLocationServiceInitializeOptions } from "../types"
|
||||
import { moduleDefinition } from "../module-definition"
|
||||
import { StockLocationServiceInitializeOptions } from "../types"
|
||||
|
||||
export const initialize = async (
|
||||
options: StockLocationServiceInitializeOptions | ExternalModuleDeclaration,
|
||||
@@ -15,7 +15,7 @@ export const initialize = async (
|
||||
}
|
||||
): Promise<IStockLocationService> => {
|
||||
const serviceKey = Modules.STOCK_LOCATION
|
||||
const loaded = await MedusaModule.bootstrap(
|
||||
const loaded = await MedusaModule.bootstrap<IStockLocationService>(
|
||||
serviceKey,
|
||||
"@medusajs/stock-location",
|
||||
options as InternalModuleDeclaration | ExternalModuleDeclaration,
|
||||
@@ -23,5 +23,5 @@ export const initialize = async (
|
||||
injectedDependencies
|
||||
)
|
||||
|
||||
return loaded[serviceKey] as IStockLocationService
|
||||
return loaded[serviceKey]
|
||||
}
|
||||
|
||||
15
packages/stock-location/src/joiner-config.ts
Normal file
15
packages/stock-location/src/joiner-config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { JoinerServiceConfig } from "@medusajs/types"
|
||||
|
||||
export const joinerConfig: JoinerServiceConfig = {
|
||||
serviceName: Modules.STOCK_LOCATION,
|
||||
primaryKeys: ["id"],
|
||||
alias: [
|
||||
{
|
||||
name: "stock_location",
|
||||
},
|
||||
{
|
||||
name: "stock_locations",
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
FilterableStockLocationProps,
|
||||
FindConfig,
|
||||
IEventBusService,
|
||||
JoinerServiceConfig,
|
||||
MODULE_RESOURCE_TYPE,
|
||||
SharedContext,
|
||||
StockLocationAddressInput,
|
||||
@@ -11,13 +12,13 @@ import {
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectEntityManager,
|
||||
isDefined,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
isDefined,
|
||||
setMetadata,
|
||||
} from "@medusajs/utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
import { joinerConfig } from "../joiner-config"
|
||||
import { StockLocation, StockLocationAddress } from "../models"
|
||||
import { buildQuery } from "../utils/build-query"
|
||||
|
||||
@@ -49,6 +50,10 @@ export default class StockLocationService {
|
||||
this.eventBusService_ = eventBusService
|
||||
}
|
||||
|
||||
__joinerConfig(): JoinerServiceConfig {
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all stock locations that match the given selector.
|
||||
* @param selector - Properties to filter by.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./common"
|
||||
export * from "./inventory"
|
||||
export * from "./service"
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
} from "./common"
|
||||
|
||||
import { FindConfig } from "../common"
|
||||
import { JoinerServiceConfig } from "../joiner"
|
||||
import { SharedContext } from ".."
|
||||
|
||||
export interface IInventoryService {
|
||||
__joinerConfig(): JoinerServiceConfig
|
||||
listInventoryItems(
|
||||
selector: FilterableInventoryItemProps,
|
||||
config?: FindConfig<InventoryItemDTO>,
|
||||
@@ -72,12 +74,12 @@ export interface IInventoryService {
|
||||
): Promise<InventoryItemDTO[]>
|
||||
|
||||
createInventoryLevel(
|
||||
data: CreateInventoryLevelInput ,
|
||||
data: CreateInventoryLevelInput,
|
||||
context?: SharedContext
|
||||
): Promise<InventoryLevelDTO>
|
||||
|
||||
|
||||
createInventoryLevels(
|
||||
data: CreateInventoryLevelInput[],
|
||||
data: CreateInventoryLevelInput[],
|
||||
context?: SharedContext
|
||||
): Promise<InventoryLevelDTO[]>
|
||||
|
||||
@@ -4,16 +4,25 @@ export type JoinerRelationship = {
|
||||
primaryKey: string
|
||||
serviceName: string
|
||||
inverse?: boolean // In an inverted relationship the foreign key is on the other service and the primary key is on the current service
|
||||
isList?: boolean // Force the relationship to return a list
|
||||
args?: Record<string, any> // Extra arguments to pass to the remoteFetchData callback
|
||||
}
|
||||
|
||||
export interface JoinerServiceConfigAlias {
|
||||
name: string
|
||||
args?: Record<string, any> // Extra arguments to pass to the remoteFetchData callback
|
||||
}
|
||||
|
||||
export interface JoinerServiceConfig {
|
||||
serviceName: string
|
||||
alias?: JoinerServiceConfigAlias | JoinerServiceConfigAlias[] // Property name to use as entrypoint to the service
|
||||
primaryKeys: string[]
|
||||
relationships?: JoinerRelationship[]
|
||||
extends?: {
|
||||
serviceName: string
|
||||
resolve: JoinerRelationship
|
||||
relationship: JoinerRelationship
|
||||
}[]
|
||||
args?: Record<string, any> // Extra arguments to pass to the remoteFetchData callback
|
||||
}
|
||||
|
||||
export interface JoinerArgument {
|
||||
@@ -23,7 +32,8 @@ export interface JoinerArgument {
|
||||
}
|
||||
|
||||
export interface RemoteJoinerQuery {
|
||||
service: string
|
||||
service?: string
|
||||
alias?: string
|
||||
expands?: Array<{
|
||||
property: string
|
||||
fields: string[]
|
||||
|
||||
@@ -31,6 +31,8 @@ export type InternalModuleDeclaration = {
|
||||
dependencies?: string[]
|
||||
resolve?: string
|
||||
options?: Record<string, unknown>
|
||||
alias?: string // If multiple modules are registered with the same key, the alias can be used to differentiate them
|
||||
main?: boolean // If the module is the main module for the key when multiple ones are registered
|
||||
}
|
||||
|
||||
export type ExternalModuleDeclaration = {
|
||||
@@ -40,6 +42,8 @@ export type ExternalModuleDeclaration = {
|
||||
url: string
|
||||
keepAlive: boolean
|
||||
}
|
||||
alias?: string // If multiple modules are registered with the same key, the alias can be used to differentiate them
|
||||
main?: boolean // If the module is the main module for the key when multiple ones are registered
|
||||
}
|
||||
|
||||
export type ModuleResolution = {
|
||||
@@ -58,6 +62,7 @@ export type ModuleDefinition = {
|
||||
label: string
|
||||
canOverride?: boolean
|
||||
isRequired?: boolean
|
||||
isQueryable?: boolean // If the modules should be queryable via Remote Joiner
|
||||
dependencies?: string[]
|
||||
defaultModuleDeclaration:
|
||||
| InternalModuleDeclaration
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./service"
|
||||
export * from "./common"
|
||||
export * from "./service"
|
||||
|
||||
@@ -11,10 +11,14 @@ import {
|
||||
ProductTagDTO,
|
||||
ProductVariantDTO,
|
||||
} from "./common"
|
||||
import { FindConfig } from "../common"
|
||||
|
||||
import { Context } from "../shared-context"
|
||||
import { FindConfig } from "../common"
|
||||
import { JoinerServiceConfig } from "../joiner"
|
||||
|
||||
export interface IProductModuleService {
|
||||
__joinerConfig(): JoinerServiceConfig
|
||||
|
||||
retrieve(productId: string, sharedContext?: Context): Promise<ProductDTO>
|
||||
|
||||
list(
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./common"
|
||||
export * from "./stock-location"
|
||||
export * from "./service"
|
||||
|
||||
43
packages/types/src/stock-location/service.ts
Normal file
43
packages/types/src/stock-location/service.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { FindConfig } from "../common/common"
|
||||
import { JoinerServiceConfig } from "../joiner"
|
||||
import { SharedContext } from "../shared-context"
|
||||
import {
|
||||
CreateStockLocationInput,
|
||||
FilterableStockLocationProps,
|
||||
StockLocationDTO,
|
||||
UpdateStockLocationInput,
|
||||
} from "./common"
|
||||
|
||||
export interface IStockLocationService {
|
||||
__joinerConfig(): JoinerServiceConfig
|
||||
list(
|
||||
selector: FilterableStockLocationProps,
|
||||
config?: FindConfig<StockLocationDTO>,
|
||||
context?: SharedContext
|
||||
): Promise<StockLocationDTO[]>
|
||||
|
||||
listAndCount(
|
||||
selector: FilterableStockLocationProps,
|
||||
config?: FindConfig<StockLocationDTO>,
|
||||
context?: SharedContext
|
||||
): Promise<[StockLocationDTO[], number]>
|
||||
|
||||
retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<StockLocationDTO>,
|
||||
context?: SharedContext
|
||||
): Promise<StockLocationDTO>
|
||||
|
||||
create(
|
||||
input: CreateStockLocationInput,
|
||||
context?: SharedContext
|
||||
): Promise<StockLocationDTO>
|
||||
|
||||
update(
|
||||
id: string,
|
||||
input: UpdateStockLocationInput,
|
||||
context?: SharedContext
|
||||
): Promise<StockLocationDTO>
|
||||
|
||||
delete(id: string, context?: SharedContext): Promise<void>
|
||||
}
|
||||
@@ -6403,7 +6403,6 @@ __metadata:
|
||||
uuid: ^9.0.0
|
||||
winston: ^3.8.2
|
||||
peerDependencies:
|
||||
"@medusajs/types": ^1.8.7
|
||||
medusa-interfaces: ^1.3.7
|
||||
typeorm: ^0.3.16
|
||||
bin:
|
||||
@@ -6415,7 +6414,8 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/modules-sdk@workspace:packages/modules-sdk"
|
||||
dependencies:
|
||||
"@medusajs/types": ^1.8.8
|
||||
"@medusajs/orchestration": ^0.0.2
|
||||
"@medusajs/types": ^1.8.11
|
||||
"@medusajs/utils": ^1.9.1
|
||||
awilix: ^8.0.0
|
||||
cross-env: ^5.2.1
|
||||
@@ -6472,11 +6472,11 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/orchestration@workspace:packages/orchestration":
|
||||
"@medusajs/orchestration@^0.0.2, @medusajs/orchestration@workspace:packages/orchestration":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/orchestration@workspace:packages/orchestration"
|
||||
dependencies:
|
||||
"@medusajs/types": ^1.8.10
|
||||
"@medusajs/types": ^1.8.11
|
||||
"@medusajs/utils": ^1.9.2
|
||||
cross-env: ^5.2.1
|
||||
graphql: ^16.6.0
|
||||
|
||||
Reference in New Issue
Block a user