* add pricing integraiton feature flag * init * first endpoint * cleanup * remove console.logs * refactor to util and implement across endpoints * add changeset * rename variables * remove mistype * feat(medusa): move price module integration to pricing service (#5322) * initial changes * chore: make product service always internal for pricing module * add notes --------- Co-authored-by: Riqwan Thamir <rmthamir@gmail.com> * nit * cleanup * update to object querying * update cart integration test * remove uppercase currency_code * nit * Feat/admin product pricing module reads (#5354) * initial changes to list prices for admin * working price module implementation of list prices * nit * variant pricing * redo integration test changes * cleanup * cleanup * fix unit tests * [wip] Core <> Pricing - price updates (#5364) * chore: update medusa-app * wip * get links and modules working with migration * wip * chore: make test pass * Feat/rule type utils (#5371) * initial rule type utils * update migration script * chore: cleanup * ensure prices are always decorated * chore: use seed instead * chore: fix oas conflict * region id add to admin price read! --------- Co-authored-by: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> Co-authored-by: Philip Korsholm <philip.korsholm@hotmail.com> * pr feedback * create remoteQueryFunction type * fix merge * fix loaders issue * Feat(medusa, types, pricing): pricing module migration script (#5409) * add migration script for money amounts in pricing module * add changeset * rename file * cleanup imports * update changeset * add check for pricing module and ff * feat(medusa,workflows,types): update prices on product and variant update (#5412) * wip * chore: update product prices through workflow * chore: cleanup * chore: update product handler updates prices for variants * chore: handle reverts * chore: address pr comments * chore: scope workflow handlers to flag handlers * chore: update return * chore: update db url * chore: remove migration * chore: increase jest timeout * Feat(medusa): update migration and initDb to run link-migrations (#5437) * initial * loader update * more progress on loaders * update integration tests and remote-query loader * remove helper * migrate isolated modules * fix test * fix integration test * update with pr feedback * unregister medusa-app * re-register medusaApp * fix featureflag * set timeout * set timeout * conditionally run link-module migrations * pr feedback 1 * add driver options for db * throw if link is not defined in migration script * pass config module directly * include container in migrate command * chore: increase timeout * rm redis from api integration tests to test * chore: temporarily skip tests * chore: undo skips + add timeout for workflow tests * chore: increase timeout for order edits * re-add redis * include final resolution * add sharedcontainer to medusaapp loader * chore: move migration under run command * try removing redis_url from api tests * chore: cleanup server on process exit * chore: clear container on exit * chore: adjustments * chore: remove consoles * chore: close express app on finish * chore: destroy pg connection on shutdown * chore: skip * chore: unskip test * chore: cleanup container pg connection * chore: skip --------- Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
237 lines
5.8 KiB
TypeScript
237 lines
5.8 KiB
TypeScript
import {
|
|
JoinerRelationship,
|
|
JoinerServiceConfig,
|
|
LoadedModule,
|
|
ModuleJoinerConfig,
|
|
RemoteExpandProperty,
|
|
RemoteJoinerQuery,
|
|
} from "@medusajs/types"
|
|
import {
|
|
RemoteFetchDataCallback,
|
|
RemoteJoiner,
|
|
toRemoteJoinerQuery,
|
|
} from "@medusajs/orchestration"
|
|
import { isString, toPascalCase } from "@medusajs/utils"
|
|
|
|
import { MedusaModule } from "./medusa-module"
|
|
|
|
export class RemoteQuery {
|
|
private remoteJoiner: RemoteJoiner
|
|
private modulesMap: Map<string, LoadedModule> = new Map()
|
|
private customRemoteFetchData?: RemoteFetchDataCallback
|
|
|
|
constructor({
|
|
modulesLoaded,
|
|
customRemoteFetchData,
|
|
servicesConfig = [],
|
|
}: {
|
|
modulesLoaded?: LoadedModule[]
|
|
customRemoteFetchData?: RemoteFetchDataCallback
|
|
servicesConfig?: ModuleJoinerConfig[]
|
|
}) {
|
|
const servicesConfig_ = [...servicesConfig]
|
|
|
|
if (!modulesLoaded?.length) {
|
|
modulesLoaded = MedusaModule.getLoadedModules().map(
|
|
(mod) => Object.values(mod)[0]
|
|
)
|
|
}
|
|
|
|
for (const mod of modulesLoaded) {
|
|
if (!mod.__definition.isQueryable) {
|
|
continue
|
|
}
|
|
|
|
const serviceName = mod.__definition.key
|
|
|
|
if (this.modulesMap.has(serviceName)) {
|
|
throw new Error(
|
|
`Duplicated instance of module ${serviceName} is not allowed.`
|
|
)
|
|
}
|
|
|
|
this.modulesMap.set(serviceName, mod)
|
|
servicesConfig_!.push(mod.__joinerConfig)
|
|
}
|
|
|
|
this.customRemoteFetchData = customRemoteFetchData
|
|
|
|
this.remoteJoiner = new RemoteJoiner(
|
|
servicesConfig_ as JoinerServiceConfig[],
|
|
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)
|
|
}
|
|
|
|
public static getAllFieldsAndRelations(
|
|
data: any,
|
|
prefix = "",
|
|
args: Record<string, unknown[]> = {}
|
|
): {
|
|
select: string[]
|
|
relations: string[]
|
|
args: Record<string, unknown[]>
|
|
} {
|
|
let fields: Set<string> = new Set()
|
|
let relations: string[] = []
|
|
|
|
data.fields?.forEach((field: string) => {
|
|
fields.add(prefix ? `${prefix}.${field}` : field)
|
|
})
|
|
args[prefix] = data.args
|
|
|
|
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,
|
|
args
|
|
)
|
|
|
|
result.select.forEach(fields.add, fields)
|
|
relations = relations.concat(result.relations)
|
|
}
|
|
}
|
|
|
|
return { select: [...fields], relations, args }
|
|
}
|
|
|
|
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
|
|
}> {
|
|
if (this.customRemoteFetchData) {
|
|
const resp = await this.customRemoteFetchData(expand, keyField, ids)
|
|
if (resp !== undefined) {
|
|
return resp
|
|
}
|
|
}
|
|
|
|
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 | RemoteJoinerQuery | object,
|
|
variables?: Record<string, unknown>
|
|
): Promise<any> {
|
|
let finalQuery: RemoteJoinerQuery = query as RemoteJoinerQuery
|
|
|
|
if (isString(query)) {
|
|
finalQuery = RemoteJoiner.parseQuery(query, variables)
|
|
} else if (!isString(finalQuery?.service) && !isString(finalQuery?.alias)) {
|
|
finalQuery = toRemoteJoinerQuery(query)
|
|
}
|
|
|
|
return await this.remoteJoiner.query(finalQuery)
|
|
}
|
|
}
|