committed by
GitHub
parent
95c538c675
commit
5a8a889c6d
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user