feat: query.index (#11348)

What:
 - `query.index` helper. It queries the index module, and aggregate the rest of requested fields/relations if needed like `query.graph`.
 
Not covered in this PR:
 - Hydrate only sub entities returned by the query. Example: 1 out of 5 variants have returned, it should only hydrate the data of the single entity, currently it will merge all the variants of the product.
 - Generate types of indexed data
 
 example:
 ```ts
 const query = container.resolve(ContainerRegistrationKeys.QUERY)
        
 await query.index({
  entity: "product",
  fields: [
    "id",
    "description",
    "status",
    "variants.sku",
    "variants.barcode",
    "variants.material",
    "variants.options.value",
    "variants.prices.amount",
    "variants.prices.currency_code",
    "variants.inventory_items.inventory.sku",
    "variants.inventory_items.inventory.description",
  ],
  filters: {
    "variants.sku": { $like: "%-1" },
    "variants.prices.amount": { $gt: 30 },
  },
  pagination: {
    order: {
      "variants.prices.amount": "DESC",
    },
  },
})
```
This query return all products where at least one variant has the title ending in `-1` and at least one price bigger than `30`.
 
The Index Module only hold the data used to paginate and filter, and the returned object is:
```json
{
  "id": "prod_01JKEAM2GJZ14K64R0DHK0JE72",
  "title": null,
  "variants": [
    {
      "id": "variant_01JKEAM2HC89GWS95F6GF9C6YA",
      "sku": "extra-variant-1",
      "prices": [
        {
          "id": "price_01JKEAM2JADEWWX72F8QDP6QXT",
          "amount": 80,
          "currency_code": "USD"
        }
      ]
    }
  ]
}
```

All the rest of the fields will be hydrated from their respective modules, and the final result will be:

```json
{
  "id": "prod_01JKEAY2RJTF8TW9A23KTGY1GD",
  "description": "extra description",
  "status": "draft",
  "variants": [
    {
      "sku": "extra-variant-1",
      "barcode": null,
      "material": null,
      "id": "variant_01JKEAY2S945CRZ6X4QZJ7GVBJ",
      "options": [
        {
          "value": "Red"
        }
      ],
      "prices": [
        {
          "amount": 20,
          "currency_code": "CAD",
          "id": "price_01JKEAY2T2EEYSWZHPGG11B7W7"
        },
        {
          "amount": 80,
          "currency_code": "USD",
          "id": "price_01JKEAY2T2NJK2E5468RK84CAR"
        }
      ],
      "inventory_items": [
        {
          "variant_id": "variant_01JKEAY2S945CRZ6X4QZJ7GVBJ",
          "inventory_item_id": "iitem_01JKEAY2SNY2AWEHPZN0DDXVW6",
          "inventory": {
            "sku": "extra-variant-1",
            "description": "extra variant 1",
            "id": "iitem_01JKEAY2SNY2AWEHPZN0DDXVW6"
          }
        }
      ]
    }
  ]
}
```

Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
Carlos R. L. RodriguesandAdrien de Peretti authored and GitHub committed 2025-02-12 12:55:09 +00:00
1 parent 8d10731343
commit 22276648ad
19 files changed
+1209 -316

No files matched your search

+10 -2
View File
@@ -1,6 +1,7 @@
import { RemoteFetchDataCallback } from "@medusajs/orchestration"
import {
ExternalModuleDeclaration,
IIndexService,
ILinkMigrationsPlanner,
InternalModuleDeclaration,
LoadedModule,
@@ -27,13 +28,13 @@ import {
promiseAll,
} from "@medusajs/utils"
import { asValue } from "awilix"
import { Link } from "./link"
import {
MedusaModule,
MigrationOptions,
ModuleBootstrapOptions,
RegisterModuleJoinerConfig,
} from "./medusa-module"
import { Link } from "./link"
import { createQuery, RemoteQuery } from "./remote-query"
import { MODULE_SCOPE } from "./types"
@@ -562,13 +563,20 @@ async function MedusaApp_({
return getMigrationPlanner(options, linkModules)
}
const indexModule = sharedContainer_.resolve(Modules.INDEX, {
allowUnregistered: true,
}) as IIndexService
return {
onApplicationShutdown,
onApplicationPrepareShutdown,
onApplicationStart,
modules: allModules,
link: remoteLink,
query: createQuery(remoteQuery) as any, // TODO: rm any once we remove the old RemoteQueryFunction and rely on the Query object instead,
query: createQuery({
remoteQuery,
indexModule,
}) as any, // TODO: rm any once we remove the old RemoteQueryFunction and rely on the Query object instead,
entitiesMap,
gqlSchema: schema,
notFound,
@@ -1,6 +1,6 @@
import { ModuleJoinerConfig } from "@medusajs/types"
import { defineJoinerConfig } from "@medusajs/utils"
import { MedusaModule } from "../../medusa-module"
import { ModuleJoinerConfig } from "@medusajs/types"
const customModuleJoinerConfig = defineJoinerConfig("custom_user", {
schema: `
@@ -62,12 +62,12 @@ const pricingJoinerConfig = defineJoinerConfig("pricing", {
}
type Price {
amount: Int
amount: Float
deep_nested_price: DeepNestedPrice
}
type DeepNestedPrice {
amount: Int
amount: Float
}
`,
alias: [
@@ -1,7 +1,9 @@
import {
GraphResultSet,
IIndexService,
RemoteJoinerOptions,
RemoteJoinerQuery,
RemoteQueryFilters,
RemoteQueryFunction,
RemoteQueryFunctionReturnPagination,
RemoteQueryInput,
@@ -21,6 +23,7 @@ import { toRemoteQuery } from "./to-remote-query"
*/
export class Query {
#remoteQuery: RemoteQuery
#indexModule: IIndexService
/**
* Method to wrap execution of the graph query for instrumentation
@@ -54,8 +57,15 @@ export class Query {
},
}
constructor(remoteQuery: RemoteQuery) {
constructor({
remoteQuery,
indexModule,
}: {
remoteQuery: RemoteQuery
indexModule: IIndexService
}) {
this.#remoteQuery = remoteQuery
this.#indexModule = indexModule
}
#unwrapQueryConfig(
@@ -172,14 +182,79 @@ export class Query {
return this.#unwrapRemoteQueryResponse(response)
}
/**
* Index function uses the Index module to query and hydrates the data with query.graph
* returns a result set
*/
async index<const TEntry extends string>(
queryOptions: RemoteQueryInput<TEntry> & {
joinFilters?: RemoteQueryFilters<TEntry>
},
options?: RemoteJoinerOptions
): Promise<GraphResultSet<TEntry>> {
if (!this.#indexModule) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Index module is not loaded."
)
}
const mainEntity = queryOptions.entity
const fields = queryOptions.fields.map((field) => mainEntity + "." + field)
const filters = queryOptions.filters
? { [mainEntity]: queryOptions.filters }
: ({} as any)
const joinFilters = queryOptions.joinFilters
? { [mainEntity]: queryOptions.joinFilters }
: ({} as any)
const pagination = queryOptions.pagination as any
if (pagination?.order) {
pagination.order = { [mainEntity]: pagination.order }
}
const indexResponse = (await this.#indexModule.query({
fields,
filters,
joinFilters,
pagination,
})) as unknown as GraphResultSet<TEntry>
delete queryOptions.pagination
delete queryOptions.filters
let finalResultset: GraphResultSet<TEntry> = indexResponse
if (indexResponse.data.length) {
finalResultset = await this.graph(queryOptions, {
...options,
initialData: indexResponse.data,
})
}
return {
data: finalResultset.data,
metadata: indexResponse.metadata as RemoteQueryFunctionReturnPagination,
}
}
}
/**
* API wrapper around the remoteQuery with backward compatibility support
* @param remoteQuery
*/
export function createQuery(remoteQuery: RemoteQuery) {
const query = new Query(remoteQuery)
export function createQuery({
remoteQuery,
indexModule,
}: {
remoteQuery: RemoteQuery
indexModule: IIndexService
}) {
const query = new Query({
remoteQuery,
indexModule,
})
function backwardCompatibleQuery(...args: any[]) {
return query.query.apply(query, args)
@@ -187,6 +262,7 @@ export function createQuery(remoteQuery: RemoteQuery) {
backwardCompatibleQuery.graph = query.graph.bind(query)
backwardCompatibleQuery.gql = query.gql.bind(query)
backwardCompatibleQuery.index = query.index.bind(query)
return backwardCompatibleQuery as Omit<RemoteQueryFunction, symbol>
}
@@ -14,9 +14,10 @@ import {
RemoteJoinerQuery,
RemoteNestedExpands,
} from "@medusajs/types"
import { isString, toPascalCase } from "@medusajs/utils"
import { isPresent, isString, toPascalCase } from "@medusajs/utils"
import { MedusaModule } from "../medusa-module"
const BASE_PREFIX = ""
export class RemoteQuery {
private remoteJoiner: RemoteJoiner
private modulesMap: Map<string, LoadedModule> = new Map()
@@ -99,7 +100,7 @@ export class RemoteQuery {
public static getAllFieldsAndRelations(
expand: RemoteExpandProperty | RemoteNestedExpands[number],
prefix = "",
prefix = BASE_PREFIX,
args: JoinerArgument = {} as JoinerArgument
): {
select?: string[]
@@ -122,7 +123,14 @@ export class RemoteQuery {
fields.add(prefix ? `${prefix}.${field}` : field)
}
args[prefix] = expand.args
const filters =
expand.args?.find((arg) => arg.name === "filters")?.value ?? {}
if (isPresent(filters)) {
args[prefix] = filters
} else if (isPresent(expand.args)) {
args[prefix] = expand.args
}
for (const property in expand.expands ?? {}) {
const newPrefix = prefix ? `${prefix}.${property}` : property
@@ -147,7 +155,12 @@ export class RemoteQuery {
: shouldSelectAll
? undefined
: []
return { select, relations, args }
return {
select,
relations,
args,
}
}
private hasPagination(options: { [attr: string]: unknown }): boolean {
@@ -225,6 +238,15 @@ export class RemoteQuery {
filters[keyField] = ids
}
delete options.args?.[BASE_PREFIX]
if (Object.keys(options.args ?? {}).length) {
filters = {
...filters,
...options?.args,
}
options.args = {} as any
}
const hasPagination = this.hasPagination(options)
let methodName = hasPagination ? "listAndCount" : "list"
@@ -178,7 +178,7 @@ export const remoteJoinerData = {
date: "2023-04-01T12:00:00Z",
products: [
{
product_id: [101, 103],
product_id: 103,
variant_id: 993,
quantity: 4,
},
@@ -59,7 +59,7 @@ export const serviceConfigs: JoinerServiceConfig[] = [
fieldAlias: {
user_shortcut: "product.user",
},
primaryKeys: ["id"],
primaryKeys: ["id,product_id"],
relationships: [
{
foreignKey: "product_id",
@@ -291,17 +291,11 @@ describe("RemoteJoiner", () => {
date: "2023-04-01T12:00:00Z",
products: [
{
product_id: [101, 103],
product: [
{
name: "Product 1",
id: 101,
},
{
name: "Product 3",
id: 103,
},
],
product_id: 103,
product: {
name: "Product 3",
id: 103,
},
},
],
user_id: 1,
@@ -438,19 +432,18 @@ describe("RemoteJoiner", () => {
number: "ORD-202",
products: [
{
product_id: [101, 103],
product_id: 103,
variant_id: 993,
quantity: 4,
product: [
{
name: "Product 1",
id: 101,
},
{
name: "Product 3",
id: 103,
},
],
product: {
name: "Product 3",
id: 103,
},
variant: {
id: 993,
name: "Product variant 33",
product_id: 103,
},
},
],
id: 205,
@@ -468,19 +461,18 @@ describe("RemoteJoiner", () => {
number: "ORD-202",
products: [
{
product_id: [101, 103],
product_id: 103,
variant_id: 993,
quantity: 4,
product: [
{
name: "Product 1",
id: 101,
},
{
name: "Product 3",
id: 103,
},
],
product: {
name: "Product 3",
id: 103,
},
variant: {
id: 993,
name: "Product variant 33",
product_id: 103,
},
},
],
id: 205,
@@ -518,16 +510,10 @@ describe("RemoteJoiner", () => {
],
}),
expect.objectContaining({
product_user_alias: [
{
email: "janedoe@example.com",
id: 2,
},
{
email: "aaa@example.com",
id: 3,
},
],
product_user_alias: {
email: "aaa@example.com",
id: 3,
},
}),
])
expect(data[0].products[0].product).toEqual(undefined)
@@ -564,16 +550,10 @@ describe("RemoteJoiner", () => {
],
}),
expect.objectContaining({
product_user_alias: [
{
email: "janedoe@example.com",
id: 2,
},
{
email: "aaa@example.com",
id: 3,
},
],
product_user_alias: {
email: "aaa@example.com",
id: 3,
},
}),
])
expect(data[0].products[0].product).toEqual({
@@ -619,18 +599,11 @@ describe("RemoteJoiner", () => {
],
}),
expect.objectContaining({
product_user_alias: [
{
name: "Jane Doe",
id: 2,
email: "janedoe@example.com",
},
{
name: "aaa bbb",
id: 3,
email: "aaa@example.com",
},
],
product_user_alias: {
name: "aaa bbb",
id: 3,
email: "aaa@example.com",
},
}),
])
expect(data[0].products[0].product).toEqual({
@@ -725,16 +698,10 @@ describe("RemoteJoiner", () => {
expect(data[1]).toEqual(
expect.objectContaining({
product_user_alias: [
{
id: 2,
name: "Jane Doe",
},
{
id: 3,
name: "aaa bbb",
},
],
product_user_alias: {
id: 3,
name: "aaa bbb",
},
})
)
@@ -857,10 +824,13 @@ describe("RemoteJoiner", () => {
id
number
products {
product {
handler
user {
name
variant {
name
product {
handler
user {
name
}
}
}
}
@@ -878,18 +848,22 @@ describe("RemoteJoiner", () => {
products: [
{
product_id: 101,
variant_id: 991,
color: "red",
product: {
id: 101,
product_extra_field: "extra 101 - red",
variant: {
id: 991,
product_id: 101,
variant_extra_field: "extra 101 - var 991 - red",
},
},
{
product_id: 101,
variant_id: 992,
color: "green",
product: {
id: 101,
product_extra_field: "extra 101 - green",
variant: {
id: 992,
product_id: 101,
variant_extra_field: "extra 101 - var 992 - green",
},
},
],
@@ -897,23 +871,6 @@ describe("RemoteJoiner", () => {
{
id: 205,
extra_field: "extra",
products: [
{
product_id: [101, 103],
product: [
{
id: 101,
color: "blue",
product_extra_field: "extra 101 - blue",
},
{
id: 103,
color: "yellow",
product_extra_field: "extra 101 - yellow",
},
],
},
],
},
]
@@ -928,29 +885,41 @@ describe("RemoteJoiner", () => {
products: [
{
product_id: 101,
variant_id: 991,
color: "red",
product: {
id: 101,
product_extra_field: "extra 101 - red",
handler: "product-1-handler",
user_id: 2,
user: {
name: "Jane Doe",
id: 2,
variant: {
id: 991,
product_id: 101,
variant_extra_field: "extra 101 - var 991 - red",
name: "Product variant 1",
product: {
handler: "product-1-handler",
id: 101,
user_id: 2,
user: {
name: "Jane Doe",
id: 2,
},
},
},
},
{
product_id: 101,
variant_id: 992,
color: "green",
product: {
id: 101,
product_extra_field: "extra 101 - green",
handler: "product-1-handler",
user_id: 2,
user: {
name: "Jane Doe",
id: 2,
variant: {
id: 992,
product_id: 101,
variant_extra_field: "extra 101 - var 992 - green",
name: "Product variant 2",
product: {
handler: "product-1-handler",
id: 101,
user_id: 2,
user: {
name: "Jane Doe",
id: 2,
},
},
},
},
@@ -965,31 +934,22 @@ describe("RemoteJoiner", () => {
number: "ORD-202",
products: [
{
product_id: [101, 103],
product: [
{
id: 101,
color: "blue",
product_extra_field: "extra 101 - blue",
handler: "product-1-handler",
user_id: 2,
user: {
name: "Jane Doe",
id: 2,
},
},
{
id: 103,
color: "yellow",
product_extra_field: "extra 101 - yellow",
variant_id: 993,
product_id: 103,
variant: {
name: "Product variant 33",
id: 993,
product_id: 103,
product: {
handler: "product-3-handler",
id: 103,
user_id: 3,
user: {
name: "aaa bbb",
id: 3,
},
},
],
},
},
],
extra_field: "extra",
@@ -44,6 +44,7 @@ type InternalParseExpandsParams = {
expands: RemoteJoinerQuery["expands"]
implodeMapping: InternalImplodeMapping[]
options?: RemoteJoinerOptions
initialData?: any[]
}
export class RemoteJoiner {
@@ -799,7 +800,9 @@ export class RemoteJoiner {
})
for (const values of fieldsById.values()) {
values.forEach((v) => idsToFetch.add(v))
values.forEach((val) => {
idsToFetch.add(val)
})
}
if (idsToFetch.size === 0) {
@@ -878,10 +881,17 @@ export class RemoteJoiner {
private parseExpands(
params: InternalParseExpandsParams
): Map<string, RemoteExpandProperty> {
const { initialService, query, serviceConfig, expands, implodeMapping } =
params
const {
initialService,
query,
serviceConfig,
expands,
implodeMapping,
options,
initialData,
} = params
const parsedExpands = this.parseProperties({
const { parsedExpands, aliasRealPathMap } = this.parseProperties({
initialService,
query,
serviceConfig,
@@ -889,6 +899,14 @@ export class RemoteJoiner {
implodeMapping,
})
if (initialData?.length) {
this.createFilterFromInitialData({
initialData: options?.initialData as any,
parsedExpands,
aliasRealPathMap,
})
}
const groupedExpands = this.groupExpands(parsedExpands)
return groupedExpands
@@ -900,7 +918,10 @@ export class RemoteJoiner {
serviceConfig: InternalJoinerServiceConfig
expands: RemoteJoinerQuery["expands"]
implodeMapping: InternalImplodeMapping[]
}): Map<string, RemoteExpandProperty> {
}): {
parsedExpands: Map<string, RemoteExpandProperty>
aliasRealPathMap: Map<string, string[]>
} {
const { initialService, query, serviceConfig, expands, implodeMapping } =
params
@@ -1039,7 +1060,7 @@ export class RemoteJoiner {
}
}
return parsedExpands
return { parsedExpands, aliasRealPathMap }
}
private getEntity({ entity, prop }: { entity: string; prop: string }) {
@@ -1216,6 +1237,206 @@ export class RemoteJoiner {
return mergedExpands
}
private createFilterFromInitialData({
initialData,
parsedExpands,
aliasRealPathMap,
}: {
initialData: any[]
parsedExpands: Map<string, RemoteExpandProperty>
aliasRealPathMap: Map<string, string[]>
}): void {
if (!initialData.length) {
return
}
const getPkValues = ({
initialData,
serviceConfig,
relationship,
}: {
initialData: any[]
serviceConfig: InternalJoinerServiceConfig
relationship?: JoinerRelationship
}): Record<string, any> => {
if (!initialData.length || !relationship || !serviceConfig) {
return {}
}
const primaryKeys = relationship.primaryKey
? relationship.primaryKey.split(",")
: serviceConfig.primaryKeys
const filter: Record<string, any> = {}
// Collect IDs for the current level, considering composed keys
primaryKeys.forEach((key) => {
filter[key] = Array.from(
new Set(initialData.map((dt) => dt[key]).filter(isDefined))
)
})
return filter
}
const parsedSegment = new Map<string, any>()
const aliasReversePathMap = new Map<string, string>(
Array.from(aliasRealPathMap).map(([path, realPath]) => [
realPath.join("."),
path,
])
)
for (let [path, expand] of parsedExpands.entries()) {
const serviceConfig = expand.serviceConfig
const relationship =
this.getEntityRelationship({
parentServiceConfig: expand.parentConfig!,
property: expand.property,
}) ?? serviceConfig.relationships?.get(serviceConfig.serviceName)
if (!serviceConfig || !relationship) {
continue
}
let aliasToPath: string | null = null
if (aliasReversePathMap.has(path)) {
aliasToPath = path
path = aliasReversePathMap.get(path)!
}
const pathSegments = path.split(".")
let relevantInitialData = initialData
let fullPath: string[] = []
for (const segment of pathSegments) {
fullPath.push(segment)
if (segment === BASE_PATH) {
continue
}
const pathStr = fullPath.join(".")
if (parsedSegment.has(pathStr)) {
relevantInitialData = parsedSegment.get(pathStr)
continue
}
relevantInitialData =
RemoteJoiner.getNestedItems(relevantInitialData, segment) ?? []
parsedSegment.set(pathStr, relevantInitialData)
if (!relevantInitialData.length) {
break
}
}
if (!relevantInitialData.length) {
continue
}
const queryPath = expand.parent === "" ? BASE_PATH : aliasToPath ?? path
const filter = getPkValues({
initialData: relevantInitialData,
serviceConfig,
relationship,
})
if (!Object.keys(filter).length) {
continue
}
const parsed = parsedExpands.get(queryPath)!
parsed.args ??= []
parsed.args.push({
name: "filters",
value: filter,
})
}
}
private mergeInitialData({
items,
initialData,
serviceConfig,
path,
expands,
relationship,
}: {
items: any[]
initialData: any[]
serviceConfig: InternalJoinerServiceConfig
path: string
expands?: RemoteNestedExpands
relationship?: JoinerRelationship
}) {
if (!initialData.length || !relationship) {
return items
}
const primaryKeys = relationship?.primaryKey.split(",") || [
serviceConfig.primaryKeys[0],
]
const expandKeys = Object.keys(expands ?? {})
const initialDataIndexMap = new Map(
initialData.map((dt, index) => [
primaryKeys.map((key) => dt[key]).join(","),
index,
])
)
const itemMap = new Map(
items.map((item) => [primaryKeys.map((key) => item[key]).join(","), item])
)
const orderedMergedItems = new Array(initialData.length)
for (const [key, index] of initialDataIndexMap.entries()) {
const iniData = initialData[index]
const item = itemMap.get(key)
if (!item) {
orderedMergedItems[index] = iniData
continue
}
// Only merge properties that are not relations
const shallowProperty = { ...iniData }
for (const key of expandKeys) {
const isRel = !!this.getEntityRelationship({
parentServiceConfig: serviceConfig,
property: key,
})
if (isRel) {
delete shallowProperty[key]
}
}
Object.assign(item, shallowProperty)
orderedMergedItems[index] = item
}
if (expands) {
for (const expand of expandKeys) {
this.mergeInitialData({
items: items.flatMap((dt) => dt[expand] ?? []),
initialData: initialData
.flatMap((dt) => dt[expand] ?? [])
.filter(isDefined),
serviceConfig,
path: `${path}.${expand}`,
expands: expands[expand]?.expands,
relationship: this.getEntityRelationship({
parentServiceConfig: serviceConfig,
property: expand,
}),
})
}
}
return orderedMergedItems
}
async query(
queryObj: RemoteJoinerQuery,
options?: RemoteJoinerOptions
@@ -1239,20 +1460,35 @@ export class RemoteJoiner {
: [options.initialData]
: []
if (options?.initialData) {
let pkName = serviceConfig.primaryKeys[0]
queryObj.args ??= []
queryObj.args.push({
name: pkName,
value: iniDataArray.map((dt) => dt[pkName]),
})
const implodeMapping: InternalImplodeMapping[] = []
const parseExpandsConfig: InternalParseExpandsParams = {
initialService: {
property: "",
parent: "",
serviceConfig,
entity: serviceConfig.entity,
fields: queryObj.fields,
},
query: queryObj,
serviceConfig,
expands: queryObj.expands!,
implodeMapping,
options,
initialData: iniDataArray,
}
const parsedExpands = this.parseExpands(parseExpandsConfig)
const root = parsedExpands.get(BASE_PATH)!
const { primaryKeyArg, otherArgs, pkName } = gerPrimaryKeysAndOtherFilters({
serviceConfig,
queryObj,
})
if (otherArgs) {
parseExpandsConfig.initialService.args = otherArgs
}
if (options?.throwIfKeyNotFound) {
if (primaryKeyArg?.value == undefined) {
if (!primaryKeyArg) {
@@ -1275,30 +1511,6 @@ export class RemoteJoiner {
}
}
const implodeMapping: InternalImplodeMapping[] = []
const parseExpandsConfig: InternalParseExpandsParams = {
initialService: {
property: "",
parent: "",
serviceConfig,
entity: serviceConfig.entity,
fields: queryObj.fields,
},
query: queryObj,
serviceConfig,
expands: queryObj.expands!,
implodeMapping,
options,
}
if (otherArgs) {
parseExpandsConfig.initialService.args = otherArgs
}
const parsedExpands = this.parseExpands(parseExpandsConfig)
const root = parsedExpands.get(BASE_PATH)!
const response = await this.fetchData({
expand: root,
pkField: pkName,
@@ -1306,28 +1518,40 @@ export class RemoteJoiner {
options,
})
const data = response.path ? response.data[response.path!] : response.data
let data = response.path ? response.data[response.path!] : response.data
const isDataArray = Array.isArray(data)
data = isDataArray ? data : [data]
if (options?.initialData) {
// merge initial data with fetched data matching the primary key
const initialDataMap = new Map(iniDataArray.map((dt) => [dt[pkName], dt]))
for (const resData of data) {
const iniData = initialDataMap.get(resData[pkName])
data = this.mergeInitialData({
items: data,
initialData: iniDataArray,
serviceConfig,
path: BASE_PATH,
expands: parsedExpands.get(BASE_PATH)?.expands,
relationship: serviceConfig.relationships?.get(
serviceConfig.serviceName
) as JoinerRelationship,
})
if (iniData) {
Object.assign(resData, iniData)
}
}
delete options?.initialData
}
await this.handleExpands({
items: Array.isArray(data) ? data : [data],
items: data,
parsedExpands,
implodeMapping,
options,
})
const retData = isDataArray ? data : data[0]
if (response.path) {
response.data[response.path] = retData
} else {
response.data = retData
}
return response.data
}
}
@@ -1350,10 +1574,10 @@ function gerPrimaryKeysAndOtherFilters({ serviceConfig, queryObj }): {
(arg) => !serviceConfig.primaryKeys.includes(arg.name)
)
const filters =
queryObj.args?.find((arg) => arg.name === "filters")?.value ?? {}
if (!primaryKeyArg) {
const filters =
queryObj.args?.find((arg) => arg.name === "filters")?.value ?? {}
const primaryKeyFilter = Object.keys(filters).find((key) => {
return serviceConfig.primaryKeys.includes(key)
})
@@ -20,7 +20,7 @@ export type IndexQueryConfig<TEntry extends string> = {
export type QueryFunctionReturnPagination = {
skip?: number
take?: number
count: number
count?: number
}
/**
@@ -6,6 +6,7 @@ import {
RemoteQueryObjectConfig,
RemoteQueryObjectFromStringResult,
} from "./remote-query-object-from-string"
import { RemoteQueryFilters } from "./to-remote-query"
/*type ExcludedProps = "__typename"*/
@@ -38,6 +39,20 @@ export type QueryGraphFunction = {
): Promise<Prettify<GraphResultSet<TEntry>>>
}
/**
* QueryIndexFunction is a wrapper on top of remoteQuery
* that simplifies the input it accepts and returns
* a normalized/consistent output.
*/
export type QueryIndexFunction = {
<const TEntry extends string>(
queryOptions: RemoteQueryInput<TEntry> & {
joinFilters?: RemoteQueryFilters<TEntry>
},
options?: RemoteJoinerOptions
): Promise<Prettify<GraphResultSet<TEntry>>>
}
/*export type RemoteQueryReturnedData<TEntry extends string> =
TEntry extends keyof RemoteQueryEntryPoints
? Prettify<Omit<RemoteQueryEntryPoints[TEntry], ExcludedProps>>
@@ -102,6 +117,12 @@ export type RemoteQueryFunction = {
*/
graph: QueryGraphFunction
/**
* Index function uses the index module to query and remoteQuery to hydrate the data
* returns a result set
*/
index: QueryIndexFunction
/**
* Query wrapper to provide specific GraphQL like API around remoteQuery.query
* @param query