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:
committed by
GitHub
parent
8d10731343
commit
22276648ad
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user