feat(orchestration): Remote Joiner field aliases (#5013)
* initial commit * chore: unit tests and forward arguments
This commit is contained in:
1 parent
834da5c41a
commit
0953bdfe84
4 files changed
+498
-116
No files matched your search
@@ -56,6 +56,9 @@ export const serviceConfigs: JoinerServiceConfig[] = [
|
|||||||
alias: {
|
alias: {
|
||||||
name: "variant",
|
name: "variant",
|
||||||
},
|
},
|
||||||
|
fieldAlias: {
|
||||||
|
user_shortcut: "product.user",
|
||||||
|
},
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
relationships: [
|
relationships: [
|
||||||
{
|
{
|
||||||
@@ -75,6 +78,12 @@ export const serviceConfigs: JoinerServiceConfig[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
serviceName: "order",
|
serviceName: "order",
|
||||||
|
fieldAlias: {
|
||||||
|
product_user_alias: {
|
||||||
|
path: "products.product.user",
|
||||||
|
forwardArgumentsOnPath: ["products.product"],
|
||||||
|
},
|
||||||
|
},
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
relationships: [
|
relationships: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,31 +38,40 @@ const container = {
|
|||||||
},
|
},
|
||||||
} as MedusaContainer
|
} as MedusaContainer
|
||||||
|
|
||||||
const fetchServiceDataCallback = async (
|
const callbacks = jest.fn()
|
||||||
expand: RemoteExpandProperty,
|
const fetchServiceDataCallback = jest.fn(
|
||||||
pkField: string,
|
async (
|
||||||
ids?: (unknown | unknown[])[],
|
expand: RemoteExpandProperty,
|
||||||
relationship?: any
|
pkField: string,
|
||||||
) => {
|
ids?: (unknown | unknown[])[],
|
||||||
const serviceConfig = expand.serviceConfig
|
relationship?: any
|
||||||
const moduleRegistryName = !serviceConfig.serviceName.endsWith("Service")
|
) => {
|
||||||
? lowerCaseFirst(serviceConfig.serviceName) + "Service"
|
const serviceConfig = expand.serviceConfig
|
||||||
: serviceConfig.serviceName
|
const moduleRegistryName = !serviceConfig.serviceName.endsWith("Service")
|
||||||
|
? lowerCaseFirst(serviceConfig.serviceName) + "Service"
|
||||||
|
: serviceConfig.serviceName
|
||||||
|
|
||||||
const service = container.resolve(moduleRegistryName)
|
const service = container.resolve(moduleRegistryName)
|
||||||
const methodName = relationship?.inverse
|
const methodName = relationship?.inverse
|
||||||
? `getBy${toPascalCase(pkField)}`
|
? `getBy${toPascalCase(pkField)}`
|
||||||
: "list"
|
: "list"
|
||||||
|
|
||||||
return await service[methodName]({
|
callbacks({
|
||||||
fields: expand.fields,
|
service: serviceConfig.serviceName,
|
||||||
args: expand.args,
|
fieds: expand.fields,
|
||||||
expands: expand.expands,
|
args: expand.args,
|
||||||
options: {
|
})
|
||||||
[pkField]: ids,
|
|
||||||
},
|
return await service[methodName]({
|
||||||
})
|
fields: expand.fields,
|
||||||
}
|
args: expand.args,
|
||||||
|
expands: expand.expands,
|
||||||
|
options: {
|
||||||
|
[pkField]: ids,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
describe("RemoteJoiner", () => {
|
describe("RemoteJoiner", () => {
|
||||||
let joiner: RemoteJoiner
|
let joiner: RemoteJoiner
|
||||||
@@ -161,13 +170,11 @@ describe("RemoteJoiner", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await joiner.query(query)
|
const data = await joiner.query(query)
|
||||||
|
|
||||||
expect(data).toEqual([
|
expect(data).toEqual([
|
||||||
{
|
{
|
||||||
email: "johndoe@example.com",
|
email: "johndoe@example.com",
|
||||||
products: [
|
products: [
|
||||||
{
|
{
|
||||||
id: 1,
|
|
||||||
product_id: 102,
|
product_id: 102,
|
||||||
product: {
|
product: {
|
||||||
name: "Product 2",
|
name: "Product 2",
|
||||||
@@ -180,7 +187,6 @@ describe("RemoteJoiner", () => {
|
|||||||
email: "janedoe@example.com",
|
email: "janedoe@example.com",
|
||||||
products: [
|
products: [
|
||||||
{
|
{
|
||||||
id: 2,
|
|
||||||
product_id: [101, 102],
|
product_id: [101, 102],
|
||||||
product: [
|
product: [
|
||||||
{
|
{
|
||||||
@@ -202,7 +208,6 @@ describe("RemoteJoiner", () => {
|
|||||||
email: "444444@example.com",
|
email: "444444@example.com",
|
||||||
products: [
|
products: [
|
||||||
{
|
{
|
||||||
id: 4,
|
|
||||||
product_id: 103,
|
product_id: 103,
|
||||||
product: {
|
product: {
|
||||||
name: "Product 3",
|
name: "Product 3",
|
||||||
@@ -272,7 +277,6 @@ describe("RemoteJoiner", () => {
|
|||||||
email: "444444@example.com",
|
email: "444444@example.com",
|
||||||
products: [
|
products: [
|
||||||
{
|
{
|
||||||
id: 4,
|
|
||||||
product_id: 103,
|
product_id: 103,
|
||||||
product: {
|
product: {
|
||||||
name: "Product 3",
|
name: "Product 3",
|
||||||
@@ -307,7 +311,6 @@ describe("RemoteJoiner", () => {
|
|||||||
email: "johndoe@example.com",
|
email: "johndoe@example.com",
|
||||||
products: [
|
products: [
|
||||||
{
|
{
|
||||||
id: 1,
|
|
||||||
product_id: 102,
|
product_id: 102,
|
||||||
product: {
|
product: {
|
||||||
name: "Product 2",
|
name: "Product 2",
|
||||||
@@ -489,4 +492,252 @@ describe("RemoteJoiner", () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("Should query an field alias and cleanup unused nested levels", async () => {
|
||||||
|
const query = RemoteJoiner.parseQuery(`
|
||||||
|
query {
|
||||||
|
order {
|
||||||
|
product_user_alias {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
const data = await joiner.query(query)
|
||||||
|
|
||||||
|
expect(data).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
product_user_alias: [
|
||||||
|
{
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
product_user_alias: [
|
||||||
|
{
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: "aaa@example.com",
|
||||||
|
id: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
expect(data[0].products[0].product).toEqual(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should query an field alias and keep queried nested levels", async () => {
|
||||||
|
const query = RemoteJoiner.parseQuery(`
|
||||||
|
query {
|
||||||
|
order {
|
||||||
|
product_user_alias {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
products {
|
||||||
|
product {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
const data = await joiner.query(query)
|
||||||
|
|
||||||
|
expect(data).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
product_user_alias: [
|
||||||
|
{
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
product_user_alias: [
|
||||||
|
{
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: "aaa@example.com",
|
||||||
|
id: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
expect(data[0].products[0].product).toEqual({
|
||||||
|
name: "Product 1",
|
||||||
|
id: 101,
|
||||||
|
user_id: 2,
|
||||||
|
})
|
||||||
|
expect(data[0].products[0].product.user).toEqual(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should query an field alias and merge requested fields on alias and on the relationship", async () => {
|
||||||
|
const query = RemoteJoiner.parseQuery(`
|
||||||
|
query {
|
||||||
|
order {
|
||||||
|
product_user_alias {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
products {
|
||||||
|
product {
|
||||||
|
user {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
const data = await joiner.query(query)
|
||||||
|
|
||||||
|
expect(data).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
product_user_alias: [
|
||||||
|
{
|
||||||
|
name: "Jane Doe",
|
||||||
|
id: 2,
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Jane Doe",
|
||||||
|
id: 2,
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
product_user_alias: [
|
||||||
|
{
|
||||||
|
name: "Jane Doe",
|
||||||
|
id: 2,
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aaa bbb",
|
||||||
|
id: 3,
|
||||||
|
email: "aaa@example.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
expect(data[0].products[0].product).toEqual({
|
||||||
|
id: 101,
|
||||||
|
user_id: 2,
|
||||||
|
user: {
|
||||||
|
name: "Jane Doe",
|
||||||
|
id: 2,
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should query multiple aliases and pass the arguments where defined on 'forwardArgumentsOnPath'", async () => {
|
||||||
|
const query = RemoteJoiner.parseQuery(`
|
||||||
|
query {
|
||||||
|
order {
|
||||||
|
id
|
||||||
|
product_user_alias (arg: { random: 123 }) {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
products {
|
||||||
|
variant {
|
||||||
|
user_shortcut(arg: 123) {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
const data = await joiner.query(query)
|
||||||
|
|
||||||
|
expect(callbacks.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
service: "order",
|
||||||
|
fieds: ["id", "product_user_alias", "products"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
service: "variantService",
|
||||||
|
fieds: ["user_shortcut", "id", "product_id"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
service: "product",
|
||||||
|
fieds: ["id", "user_id"],
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "arg",
|
||||||
|
value: {
|
||||||
|
random: 123,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
service: "user",
|
||||||
|
fieds: ["name", "id"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
service: "product",
|
||||||
|
fieds: ["id", "user_id"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
service: "user",
|
||||||
|
fieds: ["email", "id"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(data[1]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
product_user_alias: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Jane Doe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "aaa bbb",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(data[0].products[0]).toEqual({
|
||||||
|
variant_id: 991,
|
||||||
|
product_id: 101,
|
||||||
|
variant: {
|
||||||
|
id: 991,
|
||||||
|
product_id: 101,
|
||||||
|
user_shortcut: {
|
||||||
|
email: "janedoe@example.com",
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
RemoteNestedExpands,
|
RemoteNestedExpands,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
|
||||||
import { isDefined } from "@medusajs/utils"
|
import { isDefined, isString } from "@medusajs/utils"
|
||||||
import GraphQLParser from "./graphql-ast"
|
import GraphQLParser from "./graphql-ast"
|
||||||
|
|
||||||
const BASE_PATH = "_root"
|
const BASE_PATH = "_root"
|
||||||
@@ -26,6 +26,12 @@ export type RemoteFetchDataCallback = (
|
|||||||
export class RemoteJoiner {
|
export class RemoteJoiner {
|
||||||
private serviceConfigCache: Map<string, JoinerServiceConfig> = new Map()
|
private serviceConfigCache: Map<string, JoinerServiceConfig> = new Map()
|
||||||
|
|
||||||
|
private implodeMapping: {
|
||||||
|
location: string[]
|
||||||
|
property: string
|
||||||
|
path: string[]
|
||||||
|
}[] = []
|
||||||
|
|
||||||
private static filterFields(
|
private static filterFields(
|
||||||
data: any,
|
data: any,
|
||||||
fields: string[],
|
fields: string[],
|
||||||
@@ -76,9 +82,7 @@ export class RemoteJoiner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static getNestedItems(items: any[], property: string): any[] {
|
private static getNestedItems(items: any[], property: string): any[] {
|
||||||
return items
|
return items.flatMap((item) => item?.[property])
|
||||||
.flatMap((item) => item[property])
|
|
||||||
.filter((item) => item !== undefined)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static createRelatedDataMap(
|
private static createRelatedDataMap(
|
||||||
@@ -265,6 +269,8 @@ export class RemoteJoiner {
|
|||||||
} else {
|
} else {
|
||||||
uniqueIds = Array.from(new Set(uniqueIds.flat()))
|
uniqueIds = Array.from(new Set(uniqueIds.flat()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uniqueIds = uniqueIds.filter((id) => id !== undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relationship) {
|
if (relationship) {
|
||||||
@@ -295,57 +301,110 @@ export class RemoteJoiner {
|
|||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleFieldAliases(
|
||||||
|
items: any[],
|
||||||
|
parsedExpands: Map<string, RemoteExpandProperty>
|
||||||
|
) {
|
||||||
|
const getChildren = (item: any, prop: string) => {
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
return item.flatMap((currentItem) => currentItem[prop])
|
||||||
|
} else {
|
||||||
|
return item[prop]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const removeChildren = (item: any, prop: string) => {
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
item.forEach((currentItem) => delete currentItem[prop])
|
||||||
|
} else {
|
||||||
|
delete item[prop]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanup: [any, string][] = []
|
||||||
|
for (const alias of this.implodeMapping) {
|
||||||
|
const propPath = alias.path
|
||||||
|
|
||||||
|
let itemsLocation = items
|
||||||
|
for (const locationProp of alias.location) {
|
||||||
|
propPath.shift()
|
||||||
|
itemsLocation = RemoteJoiner.getNestedItems(itemsLocation, locationProp)
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsLocation.forEach((locationItem) => {
|
||||||
|
if (!locationItem) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentItems = locationItem
|
||||||
|
let parentRemoveItems: any = null
|
||||||
|
|
||||||
|
const curPath: string[] = [BASE_PATH].concat(alias.location)
|
||||||
|
for (const prop of propPath) {
|
||||||
|
if (currentItems === undefined) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
curPath.push(prop)
|
||||||
|
|
||||||
|
const config = parsedExpands.get(curPath.join(".")) as any
|
||||||
|
if (config?.isAliasMapping && parentRemoveItems === null) {
|
||||||
|
parentRemoveItems = [currentItems, prop]
|
||||||
|
}
|
||||||
|
|
||||||
|
currentItems = getChildren(currentItems, prop)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(currentItems)) {
|
||||||
|
if (currentItems.length < 2) {
|
||||||
|
locationItem[alias.property] = currentItems.shift()
|
||||||
|
} else {
|
||||||
|
locationItem[alias.property] = currentItems
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
locationItem[alias.property] = currentItems
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentRemoveItems !== null) {
|
||||||
|
cleanup.push(parentRemoveItems)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const parentRemoveItems of cleanup) {
|
||||||
|
const [remItems, path] = parentRemoveItems
|
||||||
|
removeChildren(remItems, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async handleExpands(
|
private async handleExpands(
|
||||||
items: any[],
|
items: any[],
|
||||||
query: RemoteJoinerQuery,
|
|
||||||
parsedExpands: Map<string, RemoteExpandProperty>
|
parsedExpands: Map<string, RemoteExpandProperty>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!parsedExpands) {
|
if (!parsedExpands) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const resolvedPaths = new Set<string>()
|
|
||||||
const stack: [any[], Partial<RemoteJoinerQuery>, string][] = [
|
|
||||||
[items, query, BASE_PATH],
|
|
||||||
]
|
|
||||||
|
|
||||||
while (stack.length > 0) {
|
for (const [expandedPath, expand] of parsedExpands.entries()) {
|
||||||
const [currentItems, currentQuery, basePath] = stack.pop()!
|
if (expandedPath === BASE_PATH) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for (const [expandedPath, expand] of parsedExpands.entries()) {
|
let nestedItems = items
|
||||||
const isParentPath = expandedPath.startsWith(basePath)
|
const expandedPathLevels = expandedPath.split(".")
|
||||||
|
|
||||||
if (!isParentPath || resolvedPaths.has(expandedPath)) {
|
for (let idx = 1; idx < expandedPathLevels.length - 1; idx++) {
|
||||||
continue
|
nestedItems = RemoteJoiner.getNestedItems(
|
||||||
}
|
nestedItems,
|
||||||
|
expandedPathLevels[idx]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
resolvedPaths.add(expandedPath)
|
if (nestedItems.length > 0) {
|
||||||
const property = expand.property || ""
|
await this.expandProperty(nestedItems, expand.parentConfig!, expand)
|
||||||
|
|
||||||
let curItems = currentItems
|
|
||||||
const expandedPathLevels = expandedPath.split(".")
|
|
||||||
for (let idx = 1; idx < expandedPathLevels.length - 1; idx++) {
|
|
||||||
curItems = RemoteJoiner.getNestedItems(
|
|
||||||
curItems,
|
|
||||||
expandedPathLevels[idx]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.expandProperty(curItems, expand.parentConfig!, expand)
|
|
||||||
const nestedItems = RemoteJoiner.getNestedItems(currentItems, property)
|
|
||||||
|
|
||||||
if (nestedItems.length > 0) {
|
|
||||||
const relationship = expand.serviceConfig
|
|
||||||
let nextProp = currentQuery
|
|
||||||
if (relationship) {
|
|
||||||
const relQuery = {
|
|
||||||
service: relationship.serviceName,
|
|
||||||
}
|
|
||||||
nextProp = relQuery
|
|
||||||
}
|
|
||||||
stack.push([nestedItems, nextProp, expandedPath])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.handleFieldAliases(items, parsedExpands)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async expandProperty(
|
private async expandProperty(
|
||||||
@@ -379,11 +438,9 @@ export class RemoteJoiner {
|
|||||||
const idsToFetch: any[] = []
|
const idsToFetch: any[] = []
|
||||||
|
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
const values = fieldsArray
|
const values = fieldsArray.map((field) => item?.[field])
|
||||||
.map((field) => item[field])
|
|
||||||
.filter((value) => value !== undefined)
|
|
||||||
|
|
||||||
if (values.length === fieldsArray.length && !item[relationship.alias]) {
|
if (values.length === fieldsArray.length && !item?.[relationship.alias]) {
|
||||||
if (fieldsArray.length === 1) {
|
if (fieldsArray.length === 1) {
|
||||||
if (!idsToFetch.includes(values[0])) {
|
if (!idsToFetch.includes(values[0])) {
|
||||||
idsToFetch.push(values[0])
|
idsToFetch.push(values[0])
|
||||||
@@ -424,30 +481,30 @@ export class RemoteJoiner {
|
|||||||
)
|
)
|
||||||
|
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
if (!item[relationship.alias]) {
|
if (!item || item[relationship.alias]) {
|
||||||
const itemKey = fieldsArray.map((field) => item[field]).join(",")
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(item[field])) {
|
const itemKey = fieldsArray.map((field) => item[field]).join(",")
|
||||||
item[relationship.alias] = item[field]
|
|
||||||
.map((id) => {
|
|
||||||
if (relationship.isList && !Array.isArray(relatedDataMap[id])) {
|
|
||||||
relatedDataMap[id] =
|
|
||||||
relatedDataMap[id] !== undefined ? [relatedDataMap[id]] : []
|
|
||||||
}
|
|
||||||
|
|
||||||
return relatedDataMap[id]
|
if (Array.isArray(item[field])) {
|
||||||
})
|
item[relationship.alias] = item[field].map((id) => {
|
||||||
.filter((relatedItem) => relatedItem !== undefined)
|
if (relationship.isList && !Array.isArray(relatedDataMap[id])) {
|
||||||
} else {
|
relatedDataMap[id] =
|
||||||
if (relationship.isList && !Array.isArray(relatedDataMap[itemKey])) {
|
relatedDataMap[id] !== undefined ? [relatedDataMap[id]] : []
|
||||||
relatedDataMap[itemKey] =
|
|
||||||
relatedDataMap[itemKey] !== undefined
|
|
||||||
? [relatedDataMap[itemKey]]
|
|
||||||
: []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item[relationship.alias] = relatedDataMap[itemKey]
|
return relatedDataMap[id]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (relationship.isList && !Array.isArray(relatedDataMap[itemKey])) {
|
||||||
|
relatedDataMap[itemKey] =
|
||||||
|
relatedDataMap[itemKey] !== undefined
|
||||||
|
? [relatedDataMap[itemKey]]
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item[relationship.alias] = relatedDataMap[itemKey]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -479,21 +536,65 @@ export class RemoteJoiner {
|
|||||||
const parsedExpands = new Map<string, any>()
|
const parsedExpands = new Map<string, any>()
|
||||||
parsedExpands.set(BASE_PATH, initialService)
|
parsedExpands.set(BASE_PATH, initialService)
|
||||||
|
|
||||||
|
let forwardArgumentsOnPath: string[] = []
|
||||||
for (const expand of expands || []) {
|
for (const expand of expands || []) {
|
||||||
const properties = expand.property.split(".")
|
const properties = expand.property.split(".")
|
||||||
let currentServiceConfig = serviceConfig as any
|
let currentServiceConfig = serviceConfig
|
||||||
const currentPath: string[] = []
|
const currentPath: string[] = []
|
||||||
|
|
||||||
for (const prop of properties) {
|
for (const prop of properties) {
|
||||||
|
const fieldAlias = currentServiceConfig.fieldAlias ?? {}
|
||||||
|
|
||||||
|
if (fieldAlias[prop]) {
|
||||||
|
const alias = fieldAlias[prop] as any
|
||||||
|
|
||||||
|
const path = isString(alias) ? alias : alias.path
|
||||||
|
const fullPath = currentPath.concat(path.split("."))
|
||||||
|
|
||||||
|
forwardArgumentsOnPath = forwardArgumentsOnPath.concat(
|
||||||
|
(alias?.forwardArgumentsOnPath || []).map(
|
||||||
|
(forPath) =>
|
||||||
|
BASE_PATH + "." + currentPath.concat(forPath).join(".")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.implodeMapping.push({
|
||||||
|
location: currentPath,
|
||||||
|
property: prop,
|
||||||
|
path: fullPath,
|
||||||
|
})
|
||||||
|
|
||||||
|
const extMapping = expands as unknown[]
|
||||||
|
|
||||||
|
const middlePath = path.split(".").slice(0, -1)
|
||||||
|
let curMiddlePath = currentPath
|
||||||
|
for (const path of middlePath) {
|
||||||
|
curMiddlePath = curMiddlePath.concat(path)
|
||||||
|
extMapping.push({
|
||||||
|
args: expand.args,
|
||||||
|
property: curMiddlePath.join("."),
|
||||||
|
isAliasMapping: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
extMapping.push({
|
||||||
|
...expand,
|
||||||
|
property: fullPath.join("."),
|
||||||
|
isAliasMapping: true,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
const fullPath = [BASE_PATH, ...currentPath, prop].join(".")
|
const fullPath = [BASE_PATH, ...currentPath, prop].join(".")
|
||||||
const relationship = currentServiceConfig.relationships.find(
|
const relationship = currentServiceConfig.relationships?.find(
|
||||||
(relation) => relation.alias === prop
|
(relation) => relation.alias === prop
|
||||||
)
|
)
|
||||||
|
|
||||||
let fields: string[] | undefined =
|
let fields: string[] | undefined =
|
||||||
fullPath === BASE_PATH + "." + expand.property
|
fullPath === BASE_PATH + "." + expand.property
|
||||||
? expand.fields
|
? expand.fields ?? []
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const args =
|
const args =
|
||||||
fullPath === BASE_PATH + "." + expand.property
|
fullPath === BASE_PATH + "." + expand.property
|
||||||
? expand.args
|
? expand.args
|
||||||
@@ -504,29 +605,29 @@ export class RemoteJoiner {
|
|||||||
parsedExpands.get([BASE_PATH, ...currentPath].join(".")) || query
|
parsedExpands.get([BASE_PATH, ...currentPath].join(".")) || query
|
||||||
|
|
||||||
if (parentExpand) {
|
if (parentExpand) {
|
||||||
if (parentExpand.fields) {
|
const relField = relationship.inverse
|
||||||
const relField = relationship.inverse
|
? relationship.primaryKey
|
||||||
? relationship.primaryKey
|
: relationship.foreignKey.split(".").pop()!
|
||||||
: relationship.foreignKey.split(".").pop()!
|
|
||||||
|
|
||||||
parentExpand.fields = parentExpand.fields
|
parentExpand.fields ??= []
|
||||||
.concat(relField.split(","))
|
|
||||||
.filter((field) => field !== relationship.alias)
|
|
||||||
|
|
||||||
parentExpand.fields = [...new Set(parentExpand.fields)]
|
parentExpand.fields = parentExpand.fields
|
||||||
}
|
.concat(relField.split(","))
|
||||||
|
.filter((field) => field !== relationship.alias)
|
||||||
|
|
||||||
|
parentExpand.fields = [...new Set(parentExpand.fields)]
|
||||||
|
|
||||||
if (fields) {
|
if (fields) {
|
||||||
const relField = relationship.inverse
|
const relField = relationship.inverse
|
||||||
? relationship.foreignKey.split(".").pop()!
|
? relationship.foreignKey.split(".").pop()!
|
||||||
: relationship.primaryKey
|
: relationship.primaryKey
|
||||||
fields = fields.concat(relField.split(","))
|
fields = fields.concat(relField.split(","))
|
||||||
|
|
||||||
fields = [...new Set(fields)]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentServiceConfig = this.getServiceConfig(relationship.serviceName)
|
currentServiceConfig = this.getServiceConfig(
|
||||||
|
relationship.serviceName
|
||||||
|
)!
|
||||||
|
|
||||||
if (!currentServiceConfig) {
|
if (!currentServiceConfig) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -535,16 +636,33 @@ export class RemoteJoiner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isAliasMapping = (expand as any).isAliasMapping
|
||||||
if (!parsedExpands.has(fullPath)) {
|
if (!parsedExpands.has(fullPath)) {
|
||||||
const parentPath = [BASE_PATH, ...currentPath].join(".")
|
const parentPath = [BASE_PATH, ...currentPath].join(".")
|
||||||
|
|
||||||
parsedExpands.set(fullPath, {
|
parsedExpands.set(fullPath, {
|
||||||
property: prop,
|
property: prop,
|
||||||
serviceConfig: currentServiceConfig,
|
serviceConfig: currentServiceConfig,
|
||||||
fields,
|
fields,
|
||||||
args,
|
args: isAliasMapping
|
||||||
|
? forwardArgumentsOnPath.includes(fullPath)
|
||||||
|
? args
|
||||||
|
: undefined
|
||||||
|
: args,
|
||||||
|
isAliasMapping: isAliasMapping,
|
||||||
parent: parentPath,
|
parent: parentPath,
|
||||||
parentConfig: parsedExpands.get(parentPath).serviceConfig,
|
parentConfig: parsedExpands.get(parentPath).serviceConfig,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
const exp = parsedExpands.get(fullPath)
|
||||||
|
|
||||||
|
if (forwardArgumentsOnPath.includes(fullPath) && args) {
|
||||||
|
exp.args = (exp.args || []).concat(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields) {
|
||||||
|
exp.fields = (exp.fields || []).concat(fields)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPath.push(prop)
|
currentPath.push(prop)
|
||||||
@@ -585,7 +703,7 @@ export class RemoteJoiner {
|
|||||||
targetExpand = targetExpand.expands[key] ??= {}
|
targetExpand = targetExpand.expands[key] ??= {}
|
||||||
}
|
}
|
||||||
|
|
||||||
targetExpand.fields = expand.fields
|
targetExpand.fields = [...new Set(expand.fields)]
|
||||||
targetExpand.args = expand.args
|
targetExpand.args = expand.args
|
||||||
|
|
||||||
mergedExpands.delete(path)
|
mergedExpands.delete(path)
|
||||||
@@ -648,11 +766,7 @@ export class RemoteJoiner {
|
|||||||
|
|
||||||
const data = response.path ? response.data[response.path!] : response.data
|
const data = response.path ? response.data[response.path!] : response.data
|
||||||
|
|
||||||
await this.handleExpands(
|
await this.handleExpands(Array.isArray(data) ? data : [data], parsedExpands)
|
||||||
Array.isArray(data) ? data : [data],
|
|
||||||
queryObj,
|
|
||||||
parsedExpands
|
|
||||||
)
|
|
||||||
|
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ export interface JoinerServiceConfigAlias {
|
|||||||
export interface JoinerServiceConfig {
|
export interface JoinerServiceConfig {
|
||||||
serviceName: string
|
serviceName: string
|
||||||
alias?: JoinerServiceConfigAlias | JoinerServiceConfigAlias[] // Property name to use as entrypoint to the service
|
alias?: JoinerServiceConfigAlias | JoinerServiceConfigAlias[] // Property name to use as entrypoint to the service
|
||||||
|
fieldAlias?: Record<
|
||||||
|
string,
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
path: string
|
||||||
|
forwardArgumentsOnPath: string[]
|
||||||
|
}
|
||||||
|
> // alias for deeper nested relationships (e.g. { 'price': 'prices.calculated_price_set.amount' })
|
||||||
primaryKeys: string[]
|
primaryKeys: string[]
|
||||||
relationships?: JoinerRelationship[]
|
relationships?: JoinerRelationship[]
|
||||||
extends?: {
|
extends?: {
|
||||||
|
|||||||
Reference in new issue
Block a user