feat(modules-sdk): Remote Query (#4463)

* feat: Remote Query
This commit is contained in:
Carlos R. L. Rodrigues
2023-07-19 15:35:36 -03:00
committed by GitHub
parent 95c538c675
commit 5a8a889c6d
57 changed files with 1286 additions and 423 deletions

View File

@@ -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]
}

View File

@@ -1,5 +1,5 @@
{
"extends": "./tsconfig.json",
"include": ["src"],
"include": ["src", "integration-tests"],
"exclude": ["node_modules"]
}

View File

@@ -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]
}

View File

@@ -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]
}

View File

@@ -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]
}

View File

@@ -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]
}

View 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",
},
},
],
}

View File

@@ -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

View File

@@ -42,7 +42,6 @@
"test": "jest"
},
"peerDependencies": {
"@medusajs/types": "^1.8.7",
"medusa-interfaces": "^1.3.7",
"typeorm": "^0.3.16"
},

View File

@@ -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", () => {

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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,

View File

@@ -3,3 +3,4 @@ export * from "./definitions"
export * from "./loaders"
export * from "./medusa-module"
export * from "./module-helper"
export * from "./remote-query"

View File

@@ -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."
)
})
})

View File

@@ -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
}

View 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)
)
}
}

View File

@@ -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",

View File

@@ -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",
},

View File

@@ -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: [
{

View File

@@ -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: [
{

View File

@@ -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: [
{

View File

@@ -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
)
}

View File

@@ -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]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 () => {

View File

@@ -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)

View File

@@ -0,0 +1,6 @@
import { ProductServiceInitializeOptions } from "../../src/types"
export const databaseOptions: ProductServiceInitializeOptions["database"] = {
schema: "public",
clientUrl: "medusa-products-test",
}

View File

@@ -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",

View File

@@ -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]
}

View 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",
},
},
],
}

View File

@@ -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({

View File

@@ -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 ({

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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> = {},

View File

@@ -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]
}

View 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",
},
],
}

View File

@@ -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.

View File

@@ -1,2 +1,2 @@
export * from "./common"
export * from "./inventory"
export * from "./service"

View File

@@ -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[]>

View File

@@ -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[]

View File

@@ -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

View File

@@ -1,2 +1,2 @@
export * from "./service"
export * from "./common"
export * from "./service"

View File

@@ -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(

View File

@@ -1,2 +1,2 @@
export * from "./common"
export * from "./stock-location"
export * from "./service"

View 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>
}

View File

@@ -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