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 authored and GitHub committed 2024-09-20 10:02:42 +00:00
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 = {}
) {
-140
View File
@@ -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) => {