feat(index): Provide a similar API to Query (#9193)

**What**
Align the index engine API to be similar to the Query API

## Example

```ts
        // Benefit from the same level of typing like the remote query

        const { data, metadata } = await indexEngine.query<'product'>({
          fields: [
            "product.*",
            "product.variants.*",
            "product.variants.prices.*",
          ],
          filters: {
            product: {
              variants: {
                prices: {
                  amount: { $gt: 50 },
                },
              },
            },
          },
          pagination: {
            order: {
              product: {
                variants: {
                  prices: {
                    amount: "DESC",
                  },
                },
              },
            },
          },
        })
```
This commit is contained in:
Adrien de Peretti
2024-09-20 10:02:42 +00:00
committed by GitHub
parent 1215a7c094
commit 3084008fc9
40 changed files with 1145 additions and 578 deletions
@@ -1,6 +1,7 @@
import {
Context,
Event,
IndexTypes,
RemoteQueryFunction,
Subscriber,
} from "@medusajs/types"
@@ -11,27 +12,20 @@ import {
isDefined,
MedusaContext,
MikroOrmBaseRepository as BaseRepository,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { EntityManager, SqlEntityManager } from "@mikro-orm/postgresql"
import { IndexData, IndexRelation } from "@models"
import {
EntityNameModuleConfigMap,
IndexModuleOptions,
QueryFormat,
QueryOptions,
SchemaObjectEntityRepresentation,
SchemaObjectRepresentation,
} from "@types"
import { createPartitions, QueryBuilder } from "../utils"
import { flattenObjectKeys } from "../utils/flatten-object-keys"
import { normalizeFieldsSelection } from "../utils/normalize-fields-selection"
type InjectedDependencies = {
manager: EntityManager
[ContainerRegistrationKeys.REMOTE_QUERY]: RemoteQueryFunction
[ContainerRegistrationKeys.QUERY]: RemoteQueryFunction
baseRepository: BaseRepository
}
export class PostgresProvider {
export class PostgresProvider implements IndexTypes.StorageProvider {
#isReady_: Promise<boolean>
protected readonly eventActionToMethodMap_ = {
@@ -43,29 +37,25 @@ export class PostgresProvider {
}
protected container_: InjectedDependencies
protected readonly schemaObjectRepresentation_: SchemaObjectRepresentation
protected readonly schemaObjectRepresentation_: IndexTypes.SchemaObjectRepresentation
protected readonly schemaEntitiesMap_: Record<string, any>
protected readonly moduleOptions_: IndexModuleOptions
protected readonly moduleOptions_: IndexTypes.IndexModuleOptions
protected readonly manager_: SqlEntityManager
protected readonly remoteQuery_: RemoteQueryFunction
protected readonly query_: RemoteQueryFunction
protected baseRepository_: BaseRepository
constructor(
{
manager,
[ContainerRegistrationKeys.REMOTE_QUERY]: remoteQuery,
baseRepository,
}: InjectedDependencies,
container: InjectedDependencies,
options: {
schemaObjectRepresentation: SchemaObjectRepresentation
schemaObjectRepresentation: IndexTypes.SchemaObjectRepresentation
entityMap: Record<string, any>
},
moduleOptions: IndexModuleOptions
moduleOptions: IndexTypes.IndexModuleOptions
) {
this.manager_ = manager
this.remoteQuery_ = remoteQuery
this.manager_ = container.manager
this.query_ = container.query
this.moduleOptions_ = moduleOptions
this.baseRepository_ = baseRepository
this.baseRepository_ = container.baseRepository
this.schemaObjectRepresentation_ = options.schemaObjectRepresentation
this.schemaEntitiesMap_ = options.entityMap
@@ -122,7 +112,7 @@ export class PostgresProvider {
TData extends { id: string; [key: string]: unknown }
>(
data: TData | TData[],
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
) {
const data_ = Array.isArray(data) ? data : [data]
@@ -194,120 +184,8 @@ export class PostgresProvider {
return result
}
@InjectManager("baseRepository_")
async query(
selection: QueryFormat,
options?: QueryOptions,
@MedusaContext() sharedContext: Context = {}
) {
await this.#isReady_
const { manager } = sharedContext as { manager: SqlEntityManager }
let hasPagination = false
if (
typeof options?.take === "number" ||
typeof options?.skip === "number"
) {
hasPagination = true
}
const connection = manager.getConnection()
const qb = new QueryBuilder({
schema: this.schemaObjectRepresentation_,
entityMap: this.schemaEntitiesMap_,
knex: connection.getKnex(),
selector: selection,
options,
})
const sql = qb.buildQuery(hasPagination, !!options?.keepFilteredEntities)
let resultset = await manager.execute(sql)
if (options?.keepFilteredEntities) {
const mainEntity = Object.keys(selection.select)[0]
const ids = resultset.map((r) => r[`${mainEntity}.id`])
if (ids.length) {
const selection_ = {
select: selection.select,
joinWhere: selection.joinWhere,
where: {
[`${mainEntity}.id`]: ids,
},
}
return await this.query(selection_, undefined, sharedContext)
}
}
return qb.buildObjectFromResultset(resultset)
}
@InjectManager("baseRepository_")
async queryAndCount(
selection: QueryFormat,
options?: QueryOptions,
@MedusaContext() sharedContext: Context = {}
): Promise<[Record<string, any>[], number, PerformanceEntry]> {
await this.#isReady_
const { manager } = sharedContext as { manager: SqlEntityManager }
const connection = manager.getConnection()
const qb = new QueryBuilder({
schema: this.schemaObjectRepresentation_,
entityMap: this.schemaEntitiesMap_,
knex: connection.getKnex(),
selector: selection,
options,
})
const sql = qb.buildQuery(true, !!options?.keepFilteredEntities)
performance.mark("index-query-start")
let resultset = await connection.execute(sql)
performance.mark("index-query-end")
const performanceMesurements = performance.measure(
"index-query-end",
"index-query-start"
)
const count = +(resultset[0]?.count ?? 0)
if (options?.keepFilteredEntities) {
const mainEntity = Object.keys(selection.select)[0]
const ids = resultset.map((r) => r[`${mainEntity}.id`])
if (ids.length) {
const selection_ = {
select: selection.select,
joinWhere: selection.joinWhere,
where: {
[`${mainEntity}.id`]: ids,
},
}
performance.mark("index-query-start")
resultset = await this.query(selection_, undefined, sharedContext)
performance.mark("index-query-end")
const performanceMesurements = performance.measure(
"index-query-end",
"index-query-start"
)
return [resultset, count, performanceMesurements]
}
}
return [
qb.buildObjectFromResultset(resultset),
count,
performanceMesurements,
]
}
consumeEvent(
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
): Subscriber<{ id: string }> {
return async (data: Event) => {
await this.#isReady_
@@ -325,17 +203,13 @@ export class PostgresProvider {
}
const { fields, alias } = schemaEntityObjectRepresentation
const entityData = await this.remoteQuery_(
remoteQueryObjectFromString({
entryPoint: alias,
variables: {
filters: {
id: ids,
},
},
fields: [...new Set(["id", ...fields])],
})
)
const { data: entityData } = await this.query_.graph({
entity: alias,
filters: {
id: ids,
},
fields: [...new Set(["id", ...fields])],
})
const argument = {
entity: schemaEntityObjectRepresentation.entity,
@@ -353,6 +227,91 @@ export class PostgresProvider {
}
}
@InjectManager("baseRepository_")
async query<const TEntry extends string>(
config: IndexTypes.IndexQueryConfig<TEntry>,
@MedusaContext() sharedContext: Context = {}
): Promise<IndexTypes.QueryResultSet<TEntry>> {
await this.#isReady_
const {
keepFilteredEntities,
fields = [],
filters = {},
joinFilters = {},
} = config
const { take, skip, order: inputOrderBy = {} } = config.pagination ?? {}
const select = normalizeFieldsSelection(fields)
const where = flattenObjectKeys(filters)
const joinWhere = flattenObjectKeys(joinFilters)
const orderBy = flattenObjectKeys(inputOrderBy)
const { manager } = sharedContext as { manager: SqlEntityManager }
let hasPagination = false
if (isDefined(skip)) {
hasPagination = true
}
const connection = manager.getConnection()
const qb = new QueryBuilder({
schema: this.schemaObjectRepresentation_,
entityMap: this.schemaEntitiesMap_,
knex: connection.getKnex(),
selector: {
select,
where,
joinWhere,
},
options: {
skip,
take,
keepFilteredEntities,
orderBy,
},
})
const sql = qb.buildQuery(hasPagination, !!keepFilteredEntities)
let resultSet = await manager.execute(sql)
const count = hasPagination ? +(resultSet[0]?.count ?? 0) : undefined
if (keepFilteredEntities) {
const mainEntity = Object.keys(select)[0]
const ids = resultSet.map((r) => r[`${mainEntity}.id`])
if (ids.length) {
return await this.query<TEntry>(
{
fields,
joinFilters,
filters: {
[mainEntity]: {
id: ids,
},
},
pagination: undefined,
keepFilteredEntities: false,
} as IndexTypes.IndexQueryConfig<TEntry>,
sharedContext
)
}
}
return {
data: qb.buildObjectFromResultset(
resultSet
) as IndexTypes.QueryResultSet<TEntry>["data"],
metadata: hasPagination
? {
count: count!,
skip,
take,
}
: undefined,
}
}
/**
* Create the index entry and the index relation entry when this event is emitted.
* @param entity
@@ -372,7 +331,7 @@ export class PostgresProvider {
}: {
entity: string
data: TData | TData[]
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
},
@MedusaContext() sharedContext: Context = {}
) {
@@ -466,7 +425,7 @@ export class PostgresProvider {
}: {
entity: string
data: TData | TData[]
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
},
@MedusaContext() sharedContext: Context = {}
) {
@@ -513,7 +472,7 @@ export class PostgresProvider {
}: {
entity: string
data: TData | TData[]
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
},
@MedusaContext() sharedContext: Context = {}
) {
@@ -567,7 +526,7 @@ export class PostgresProvider {
}: {
entity: string
data: TData | TData[]
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
},
@MedusaContext() sharedContext: Context = {}
) {
@@ -595,7 +554,7 @@ export class PostgresProvider {
const parentEntityName = (
this.schemaObjectRepresentation_._serviceNameModuleConfigMap[
parentServiceName
] as EntityNameModuleConfigMap[0]
] as IndexTypes.EntityNameModuleConfigMap[0]
).linkableKeys?.[parentPropertyId]
if (!parentEntityName) {
@@ -617,7 +576,7 @@ export class PostgresProvider {
const childEntityName = (
this.schemaObjectRepresentation_._serviceNameModuleConfigMap[
childServiceName
] as EntityNameModuleConfigMap[0]
] as IndexTypes.EntityNameModuleConfigMap[0]
).linkableKeys?.[childPropertyId]
if (!childEntityName) {
@@ -688,7 +647,7 @@ export class PostgresProvider {
}: {
entity: string
data: TData | TData[]
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
},
@MedusaContext() sharedContext: Context = {}
) {