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 parent
1215a7c094
commit
3084008fc9
40 files changed
+1145
-578
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
Constructor,
|
||||
IEventBusModuleService,
|
||||
IndexTypes,
|
||||
InternalModuleDeclaration,
|
||||
@@ -9,36 +10,32 @@ import {
|
||||
MikroOrmBaseRepository as BaseRepository,
|
||||
Modules,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
IndexModuleOptions,
|
||||
SchemaObjectRepresentation,
|
||||
schemaObjectRepresentationPropertiesToOmit,
|
||||
StorageProvider,
|
||||
} from "@types"
|
||||
import { schemaObjectRepresentationPropertiesToOmit } from "@types"
|
||||
import { buildSchemaObjectRepresentation } from "../utils/build-config"
|
||||
import { defaultSchema } from "../utils/default-schema"
|
||||
import { gqlSchemaToTypes } from "../utils/gql-to-types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
[Modules.EVENT_BUS]: IEventBusModuleService
|
||||
storageProviderCtr: StorageProvider
|
||||
storageProviderCtr: Constructor<IndexTypes.StorageProvider>
|
||||
[ContainerRegistrationKeys.QUERY]: RemoteQueryFunction
|
||||
storageProviderCtrOptions: unknown
|
||||
[ContainerRegistrationKeys.REMOTE_QUERY]: RemoteQueryFunction
|
||||
baseRepository: BaseRepository
|
||||
}
|
||||
|
||||
export default class IndexModuleService implements IndexTypes.IIndexService {
|
||||
private readonly container_: InjectedDependencies
|
||||
private readonly moduleOptions_: IndexModuleOptions
|
||||
private readonly moduleOptions_: IndexTypes.IndexModuleOptions
|
||||
|
||||
protected readonly eventBusModuleService_: IEventBusModuleService
|
||||
|
||||
protected schemaObjectRepresentation_: SchemaObjectRepresentation
|
||||
protected schemaObjectRepresentation_: IndexTypes.SchemaObjectRepresentation
|
||||
protected schemaEntitiesMap_: Record<string, any>
|
||||
|
||||
protected readonly storageProviderCtr_: StorageProvider
|
||||
protected readonly storageProviderCtr_: Constructor<IndexTypes.StorageProvider>
|
||||
protected readonly storageProviderCtrOptions_: unknown
|
||||
|
||||
protected storageProvider_: StorageProvider
|
||||
protected storageProvider_: IndexTypes.StorageProvider
|
||||
|
||||
constructor(
|
||||
container: InjectedDependencies,
|
||||
@@ -46,7 +43,7 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
||||
) {
|
||||
this.container_ = container
|
||||
this.moduleOptions_ = (moduleDeclaration.options ??
|
||||
moduleDeclaration) as unknown as IndexModuleOptions
|
||||
moduleDeclaration) as unknown as IndexTypes.IndexModuleOptions
|
||||
|
||||
const {
|
||||
[Modules.EVENT_BUS]: eventBusModuleService,
|
||||
@@ -65,10 +62,6 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
||||
}
|
||||
}
|
||||
|
||||
__joinerConfig() {
|
||||
return {}
|
||||
}
|
||||
|
||||
__hooks = {
|
||||
onApplicationStart(this: IndexModuleService) {
|
||||
return this.onApplicationStart_()
|
||||
@@ -86,31 +79,29 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
||||
entityMap: this.schemaEntitiesMap_,
|
||||
}),
|
||||
this.moduleOptions_
|
||||
)
|
||||
) as IndexTypes.StorageProvider
|
||||
|
||||
this.registerListeners()
|
||||
|
||||
if (this.storageProvider_.onApplicationStart) {
|
||||
await this.storageProvider_.onApplicationStart()
|
||||
}
|
||||
|
||||
await gqlSchemaToTypes(this.moduleOptions_.schema ?? defaultSchema)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
async query(...args) {
|
||||
return await this.storageProvider_.query.apply(this.storageProvider_, args)
|
||||
}
|
||||
|
||||
async queryAndCount(...args) {
|
||||
return await this.storageProvider_.queryAndCount.apply(
|
||||
this.storageProvider_,
|
||||
args
|
||||
)
|
||||
async query<const TEntry extends string>(
|
||||
config: IndexTypes.IndexQueryConfig<TEntry>
|
||||
): Promise<IndexTypes.QueryResultSet<TEntry>> {
|
||||
return await this.storageProvider_.query(config)
|
||||
}
|
||||
|
||||
protected registerListeners() {
|
||||
const schemaObjectRepresentation = this.schemaObjectRepresentation_ ?? {}
|
||||
const schemaObjectRepresentation = (this.schemaObjectRepresentation_ ??
|
||||
{}) as IndexTypes.SchemaObjectRepresentation
|
||||
|
||||
for (const [entityName, schemaEntityObjectRepresentation] of Object.entries(
|
||||
schemaObjectRepresentation
|
||||
@@ -119,7 +110,9 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
||||
continue
|
||||
}
|
||||
|
||||
schemaEntityObjectRepresentation.listeners.forEach((listener) => {
|
||||
;(
|
||||
schemaEntityObjectRepresentation as IndexTypes.SchemaObjectEntityRepresentation
|
||||
).listeners.forEach((listener) => {
|
||||
this.eventBusModuleService_.subscribe(
|
||||
listener,
|
||||
this.storageProvider_.consumeEvent(schemaEntityObjectRepresentation)
|
||||
@@ -136,6 +129,7 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
||||
const [objectRepresentation, entityMap] = buildSchemaObjectRepresentation(
|
||||
this.moduleOptions_.schema ?? defaultSchema
|
||||
)
|
||||
|
||||
this.schemaObjectRepresentation_ = objectRepresentation
|
||||
this.schemaEntitiesMap_ = entityMap
|
||||
|
||||
|
||||
@@ -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 = {}
|
||||
) {
|
||||
|
||||
@@ -1,133 +1,8 @@
|
||||
import {
|
||||
ModuleJoinerConfig,
|
||||
ModulesSdkTypes,
|
||||
Subscriber,
|
||||
} from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* Represents the module options that can be provided
|
||||
*/
|
||||
export interface IndexModuleOptions {
|
||||
customAdapter?: {
|
||||
constructor: new (...args: any[]) => any
|
||||
options: any
|
||||
}
|
||||
defaultAdapterOptions?: ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
schema: string
|
||||
}
|
||||
|
||||
export type SchemaObjectEntityRepresentation = {
|
||||
/**
|
||||
* The name of the type/entity in the schema
|
||||
*/
|
||||
entity: string
|
||||
|
||||
/**
|
||||
* All parents a type/entity refers to in the schema
|
||||
* or through links
|
||||
*/
|
||||
parents: {
|
||||
/**
|
||||
* The reference to the schema object representation
|
||||
* of the parent
|
||||
*/
|
||||
ref: SchemaObjectEntityRepresentation
|
||||
|
||||
/**
|
||||
* When a link is inferred between two types/entities
|
||||
* we are configuring the link tree, and therefore we are
|
||||
* storing the reference to the parent type/entity within the
|
||||
* schema which defer from the true parent from a pure entity
|
||||
* point of view
|
||||
*/
|
||||
inSchemaRef?: SchemaObjectEntityRepresentation
|
||||
|
||||
/**
|
||||
* The property the data should be assigned to in the parent
|
||||
*/
|
||||
targetProp: string
|
||||
|
||||
/**
|
||||
* Are the data expected to be a list or not
|
||||
*/
|
||||
isList?: boolean
|
||||
}[]
|
||||
|
||||
/**
|
||||
* The default fields to query for the type/entity
|
||||
*/
|
||||
fields: string[]
|
||||
|
||||
/**
|
||||
* @Listerners directive is required and all listeners found
|
||||
* for the type will be stored here
|
||||
*/
|
||||
listeners: string[]
|
||||
|
||||
/**
|
||||
* The alias for the type/entity retrieved in the corresponding
|
||||
* module
|
||||
*/
|
||||
alias: string
|
||||
|
||||
/**
|
||||
* The module joiner config corresponding to the module the type/entity
|
||||
* refers to
|
||||
*/
|
||||
moduleConfig: ModuleJoinerConfig
|
||||
}
|
||||
|
||||
export type SchemaPropertiesMap = {
|
||||
[key: string]: {
|
||||
shortCutOf?: string
|
||||
ref: SchemaObjectEntityRepresentation
|
||||
}
|
||||
}
|
||||
|
||||
export type EntityNameModuleConfigMap = {
|
||||
[key: string]: ModuleJoinerConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the schema objects representation once the schema has been processed
|
||||
*/
|
||||
export type SchemaObjectRepresentation =
|
||||
| {
|
||||
[key: string]: SchemaObjectEntityRepresentation
|
||||
}
|
||||
| {
|
||||
_schemaPropertiesMap: SchemaPropertiesMap
|
||||
_serviceNameModuleConfigMap: EntityNameModuleConfigMap
|
||||
}
|
||||
|
||||
export const schemaObjectRepresentationPropertiesToOmit = [
|
||||
"_schemaPropertiesMap",
|
||||
"_serviceNameModuleConfigMap",
|
||||
]
|
||||
|
||||
/**
|
||||
* Represents the storage provider interface,
|
||||
*/
|
||||
export interface StorageProvider {
|
||||
new (
|
||||
container: { [key: string]: any },
|
||||
storageProviderOptions: unknown & {
|
||||
schemaObjectRepresentation: SchemaObjectRepresentation
|
||||
},
|
||||
moduleOptions: IndexModuleOptions
|
||||
): StorageProvider
|
||||
|
||||
onApplicationStart?(): Promise<void>
|
||||
|
||||
query(...args): unknown
|
||||
|
||||
queryAndCount(...args): unknown
|
||||
|
||||
consumeEvent(
|
||||
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
|
||||
): Subscriber
|
||||
}
|
||||
|
||||
export type Select = {
|
||||
[key: string]: boolean | Select | Select[]
|
||||
}
|
||||
@@ -152,18 +27,3 @@ export type QueryOptions = {
|
||||
orderBy?: OrderBy | OrderBy[]
|
||||
keepFilteredEntities?: boolean
|
||||
}
|
||||
|
||||
// Preventing infinite depth
|
||||
type ResultSetLimit = [never | 0 | 1 | 2]
|
||||
|
||||
export type Resultset<Select, Prev extends number = 3> = Prev extends never
|
||||
? never
|
||||
: {
|
||||
[key in keyof Select]: Select[key] extends boolean
|
||||
? string
|
||||
: Select[key] extends Select[]
|
||||
? Resultset<Select[key][0], ResultSetLimit[Prev]>[]
|
||||
: Select[key] extends {}
|
||||
? Resultset<Select[key], ResultSetLimit[Prev]>
|
||||
: unknown
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { flattenObjectKeys } from "../flatten-object-keys"
|
||||
|
||||
describe("flattenWhereClauses", () => {
|
||||
it("should flatten where clauses", () => {
|
||||
const where = {
|
||||
a: 1,
|
||||
b: {
|
||||
c: 2,
|
||||
d: 3,
|
||||
z: {
|
||||
$ilike: "%test%",
|
||||
},
|
||||
y: null,
|
||||
},
|
||||
e: 4,
|
||||
}
|
||||
|
||||
const result = flattenObjectKeys(where)
|
||||
|
||||
expect(result).toEqual({
|
||||
a: 1,
|
||||
"b.c": 2,
|
||||
"b.d": 3,
|
||||
"b.z": { $ilike: "%test%" },
|
||||
"b.y": null,
|
||||
e: 4,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
import { normalizeFieldsSelection } from "../normalize-fields-selection"
|
||||
|
||||
describe("normalizeFieldsSelection", () => {
|
||||
it("should normalize fields selection", () => {
|
||||
const fields = [
|
||||
"product.id",
|
||||
"product.title",
|
||||
"product.variants.*",
|
||||
"product.variants.prices.*",
|
||||
]
|
||||
const result = normalizeFieldsSelection(fields)
|
||||
expect(result).toEqual({
|
||||
product: {
|
||||
id: true,
|
||||
title: true,
|
||||
variants: {
|
||||
prices: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,16 +5,13 @@ import {
|
||||
MedusaModule,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IndexTypes,
|
||||
JoinerServiceConfigAlias,
|
||||
ModuleJoinerConfig,
|
||||
ModuleJoinerRelationship,
|
||||
} from "@medusajs/types"
|
||||
import { CommonEvents } from "@medusajs/utils"
|
||||
import {
|
||||
SchemaObjectEntityRepresentation,
|
||||
SchemaObjectRepresentation,
|
||||
schemaObjectRepresentationPropertiesToOmit,
|
||||
} from "@types"
|
||||
import { schemaObjectRepresentationPropertiesToOmit } from "@types"
|
||||
import { Kind, ObjectTypeDefinitionNode } from "graphql/index"
|
||||
|
||||
export const CustomDirectives = {
|
||||
@@ -27,7 +24,7 @@ export const CustomDirectives = {
|
||||
},
|
||||
}
|
||||
|
||||
function makeSchemaExecutable(inputSchema: string) {
|
||||
export function makeSchemaExecutable(inputSchema: string) {
|
||||
const { schema: cleanedSchema } = cleanGraphQLSchema(inputSchema)
|
||||
|
||||
return makeExecutableSchema({ typeDefs: cleanedSchema })
|
||||
@@ -269,7 +266,7 @@ function retrieveLinkModuleAndAlias({
|
||||
function getObjectRepresentationRef(
|
||||
entityName,
|
||||
{ objectRepresentationRef }
|
||||
): SchemaObjectEntityRepresentation {
|
||||
): IndexTypes.SchemaObjectEntityRepresentation {
|
||||
return (objectRepresentationRef[entityName] ??= {
|
||||
entity: entityName,
|
||||
parents: [],
|
||||
@@ -309,7 +306,7 @@ function processEntity(
|
||||
}: {
|
||||
entitiesMap: any
|
||||
moduleJoinerConfigs: ModuleJoinerConfig[]
|
||||
objectRepresentationRef: SchemaObjectRepresentation
|
||||
objectRepresentationRef: IndexTypes.SchemaObjectRepresentation
|
||||
}
|
||||
) {
|
||||
/**
|
||||
@@ -616,8 +613,11 @@ function processEntity(
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
function buildAliasMap(objectRepresentation: SchemaObjectRepresentation) {
|
||||
const aliasMap: SchemaObjectRepresentation["_schemaPropertiesMap"] = {}
|
||||
function buildAliasMap(
|
||||
objectRepresentation: IndexTypes.SchemaObjectRepresentation
|
||||
) {
|
||||
const aliasMap: IndexTypes.SchemaObjectRepresentation["_schemaPropertiesMap"] =
|
||||
{}
|
||||
|
||||
function recursivelyBuildAliasPath(
|
||||
current,
|
||||
@@ -705,7 +705,7 @@ function buildAliasMap(objectRepresentation: SchemaObjectRepresentation) {
|
||||
*/
|
||||
export function buildSchemaObjectRepresentation(
|
||||
schema
|
||||
): [SchemaObjectRepresentation, Record<string, any>] {
|
||||
): [IndexTypes.SchemaObjectRepresentation, Record<string, any>] {
|
||||
const moduleJoinerConfigs = MedusaModule.getAllJoinerConfigs()
|
||||
const augmentedSchema = CustomDirectives.Listeners.definition + schema
|
||||
const executableSchema = makeSchemaExecutable(augmentedSchema)
|
||||
@@ -713,7 +713,7 @@ export function buildSchemaObjectRepresentation(
|
||||
|
||||
const objectRepresentation = {
|
||||
_serviceNameModuleConfigMap: {},
|
||||
} as SchemaObjectRepresentation
|
||||
} as IndexTypes.SchemaObjectRepresentation
|
||||
|
||||
Object.entries(entitiesMap).forEach(([entityName, entityMapValue]) => {
|
||||
if (!entityMapValue.astNode) {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import {
|
||||
SchemaObjectRepresentation,
|
||||
schemaObjectRepresentationPropertiesToOmit,
|
||||
} from "../types"
|
||||
import { schemaObjectRepresentationPropertiesToOmit } from "@types"
|
||||
import { IndexTypes } from "@medusajs/types"
|
||||
|
||||
export async function createPartitions(
|
||||
schemaObjectRepresentation: SchemaObjectRepresentation,
|
||||
schemaObjectRepresentation: IndexTypes.SchemaObjectRepresentation,
|
||||
manager: SqlEntityManager
|
||||
): Promise<void> {
|
||||
const activeSchema = manager.config.get("schema")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const defaultSchema = `
|
||||
type Product @Listeners(values: ["Product.product.created", "Product.product.updated", "Product.product.deleted"]) {
|
||||
type Product @Listeners(values: ["Product.product.created", "Product.product.updated", "Product.product.deleted"]) {
|
||||
id: String
|
||||
title: String
|
||||
variants: [ProductVariant]
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { OPERATOR_MAP } from "./query-builder"
|
||||
|
||||
/**
|
||||
* Flatten object keys
|
||||
* @example
|
||||
* input: {
|
||||
* a: 1,
|
||||
* b: {
|
||||
* c: 2,
|
||||
* d: 3,
|
||||
* },
|
||||
* e: 4,
|
||||
* }
|
||||
*
|
||||
* output: {
|
||||
* a: 1,
|
||||
* b.c: 2,
|
||||
* b.d: 3,
|
||||
* e: 4,
|
||||
* }
|
||||
*
|
||||
* @param input
|
||||
*/
|
||||
export function flattenObjectKeys(input: Record<string, any>) {
|
||||
const result: Record<string, any> = {}
|
||||
|
||||
function flatten(obj: Record<string, any>, path?: string) {
|
||||
for (const key in obj) {
|
||||
const isOperatorMap = !!OPERATOR_MAP[key]
|
||||
|
||||
if (isOperatorMap) {
|
||||
result[path ?? key] = obj
|
||||
continue
|
||||
}
|
||||
|
||||
const newPath = path ? `${path}.${key}` : key
|
||||
|
||||
if (obj[key] === null) {
|
||||
result[newPath] = null
|
||||
continue
|
||||
}
|
||||
|
||||
if (Array.isArray(obj[key])) {
|
||||
result[newPath] = obj[key]
|
||||
continue
|
||||
}
|
||||
|
||||
if (typeof obj[key] === "object" && !isOperatorMap) {
|
||||
flatten(obj[key], newPath)
|
||||
continue
|
||||
}
|
||||
|
||||
result[newPath] = obj[key]
|
||||
}
|
||||
}
|
||||
|
||||
flatten(input)
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { join } from "path"
|
||||
import { CustomDirectives, makeSchemaExecutable } from "./build-config"
|
||||
import {
|
||||
gqlSchemaToTypes as ModulesSdkGqlSchemaToTypes,
|
||||
MedusaModule,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { FileSystem } from "@medusajs/utils"
|
||||
import * as process from "process"
|
||||
|
||||
export async function gqlSchemaToTypes(schema: string) {
|
||||
const augmentedSchema = CustomDirectives.Listeners.definition + schema
|
||||
const executableSchema = makeSchemaExecutable(augmentedSchema)
|
||||
const filename = "index-service-entry-points"
|
||||
const filenameWithExt = filename + ".d.ts"
|
||||
const dir = join(process.cwd(), ".medusa")
|
||||
|
||||
await ModulesSdkGqlSchemaToTypes({
|
||||
schema: executableSchema,
|
||||
filename,
|
||||
outputDir: dir,
|
||||
})
|
||||
|
||||
const fileSystem = new FileSystem(dir)
|
||||
let content = await fileSystem.contents(filenameWithExt)
|
||||
|
||||
await fileSystem.remove(filenameWithExt)
|
||||
|
||||
const entryPoints = buildEntryPointsTypeMap(content)
|
||||
|
||||
const indexEntryPoints = `
|
||||
declare module '@medusajs/types' {
|
||||
interface IndexServiceEntryPoints {
|
||||
${entryPoints
|
||||
.map((entry) => ` ${entry.entryPoint}: ${entry.entityType}`)
|
||||
.join("\n")}
|
||||
}
|
||||
}`
|
||||
|
||||
const contentToReplaceMatcher = new RegExp(
|
||||
`declare\\s+module\\s+['"][^'"]+['"]\\s*{([^}]*?)}\\s+}`,
|
||||
"gm"
|
||||
)
|
||||
content = content.replace(contentToReplaceMatcher, indexEntryPoints)
|
||||
|
||||
await fileSystem.create(filenameWithExt, content)
|
||||
}
|
||||
|
||||
function buildEntryPointsTypeMap(
|
||||
schema: string
|
||||
): { entryPoint: string; entityType: any }[] {
|
||||
// build map entry point to there type to be merged and used by the remote query
|
||||
|
||||
const joinerConfigs = MedusaModule.getAllJoinerConfigs()
|
||||
return joinerConfigs
|
||||
.flatMap((config) => {
|
||||
const aliases = Array.isArray(config.alias)
|
||||
? config.alias
|
||||
: config.alias
|
||||
? [config.alias]
|
||||
: []
|
||||
|
||||
return aliases.flatMap((alias) => {
|
||||
const names = Array.isArray(alias.name) ? alias.name : [alias.name]
|
||||
const entity = alias?.["entity"]
|
||||
return names.map((aliasItem) => {
|
||||
if (!schema.includes(`export type ${entity} `)) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
entryPoint: aliasItem,
|
||||
entityType: entity
|
||||
? schema.includes(`export type ${entity} `)
|
||||
? alias?.["entity"]
|
||||
: "any"
|
||||
: "any",
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.filter(Boolean) as { entryPoint: string; entityType: any }[]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { objectFromStringPath } from "@medusajs/utils"
|
||||
|
||||
export function normalizeFieldsSelection(fields: string[]) {
|
||||
const normalizedFields = fields.map((field) => field.replace(/\.\*/g, ""))
|
||||
const fieldsObject = objectFromStringPath(normalizedFields)
|
||||
return fieldsObject
|
||||
}
|
||||
@@ -1,14 +1,21 @@
|
||||
import { isObject, isString } from "@medusajs/utils"
|
||||
import { IndexTypes } from "@medusajs/types"
|
||||
import { GraphQLList } from "graphql"
|
||||
import { Knex } from "knex"
|
||||
import {
|
||||
OrderBy,
|
||||
QueryFormat,
|
||||
QueryOptions,
|
||||
SchemaObjectRepresentation,
|
||||
SchemaPropertiesMap,
|
||||
Select,
|
||||
} from "../types"
|
||||
import { OrderBy, QueryFormat, QueryOptions, Select } from "@types"
|
||||
|
||||
export const OPERATOR_MAP = {
|
||||
$eq: "=",
|
||||
$lt: "<",
|
||||
$gt: ">",
|
||||
$lte: "<=",
|
||||
$gte: ">=",
|
||||
$ne: "!=",
|
||||
$in: "IN",
|
||||
$is: "IS",
|
||||
$like: "LIKE",
|
||||
$ilike: "ILIKE",
|
||||
}
|
||||
|
||||
export class QueryBuilder {
|
||||
private readonly structure: Select
|
||||
@@ -16,10 +23,10 @@ export class QueryBuilder {
|
||||
private readonly knex: Knex
|
||||
private readonly selector: QueryFormat
|
||||
private readonly options?: QueryOptions
|
||||
private readonly schema: SchemaObjectRepresentation
|
||||
private readonly schema: IndexTypes.SchemaObjectRepresentation
|
||||
|
||||
constructor(args: {
|
||||
schema: SchemaObjectRepresentation
|
||||
schema: IndexTypes.SchemaObjectRepresentation
|
||||
entityMap: Record<string, any>
|
||||
knex: Knex
|
||||
selector: QueryFormat
|
||||
@@ -37,7 +44,7 @@ export class QueryBuilder {
|
||||
return Object.keys(structure ?? {}).filter((key) => key !== "entity")
|
||||
}
|
||||
|
||||
private getEntity(path): SchemaPropertiesMap[0] {
|
||||
private getEntity(path): IndexTypes.SchemaPropertiesMap[0] {
|
||||
if (!this.schema._schemaPropertiesMap[path]) {
|
||||
throw new Error(`Could not find entity for path: ${path}`)
|
||||
}
|
||||
@@ -119,18 +126,6 @@ export class QueryBuilder {
|
||||
obj: object,
|
||||
builder: Knex.QueryBuilder
|
||||
) {
|
||||
const OPERATOR_MAP = {
|
||||
$eq: "=",
|
||||
$lt: "<",
|
||||
$gt: ">",
|
||||
$lte: "<=",
|
||||
$gte: ">=",
|
||||
$ne: "!=",
|
||||
$in: "IN",
|
||||
$is: "IS",
|
||||
$like: "LIKE",
|
||||
$ilike: "ILIKE",
|
||||
}
|
||||
const keys = Object.keys(obj)
|
||||
|
||||
const getPathAndField = (key: string) => {
|
||||
|
||||
Reference in new issue
Block a user