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:
@@ -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 = {}
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user