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,4 @@
|
|||||||
dist/
|
dist/
|
||||||
node_modules
|
node_modules
|
||||||
*yarn-error.log
|
*yarn-error.log
|
||||||
|
.medusa
|
||||||
@@ -58,25 +58,35 @@ medusaIntegrationTestRunner({
|
|||||||
// Timeout to allow indexing to finish
|
// Timeout to allow indexing to finish
|
||||||
await setTimeout(2000)
|
await setTimeout(2000)
|
||||||
|
|
||||||
const [results, count] = await indexEngine.queryAndCount(
|
const { data: results } = await indexEngine.query<"product">({
|
||||||
{
|
fields: [
|
||||||
select: {
|
"product.*",
|
||||||
product: {
|
"product.variants.*",
|
||||||
variants: {
|
"product.variants.prices.*",
|
||||||
prices: true,
|
],
|
||||||
|
filters: {
|
||||||
|
product: {
|
||||||
|
variants: {
|
||||||
|
prices: {
|
||||||
|
amount: { $gt: 50 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
where: {
|
},
|
||||||
"product.variants.prices.amount": { $gt: 50 },
|
pagination: {
|
||||||
|
order: {
|
||||||
|
product: {
|
||||||
|
variants: {
|
||||||
|
prices: {
|
||||||
|
amount: "DESC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
})
|
||||||
orderBy: [{ "product.variants.prices.amount": "DESC" }],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(count).toBe(1)
|
expect(results.length).toBe(1)
|
||||||
|
|
||||||
const variants = results[0].variants
|
const variants = results[0].variants
|
||||||
|
|
||||||
@@ -118,26 +128,36 @@ medusaIntegrationTestRunner({
|
|||||||
// Timeout to allow indexing to finish
|
// Timeout to allow indexing to finish
|
||||||
await setTimeout(2000)
|
await setTimeout(2000)
|
||||||
|
|
||||||
const [results, count] = await indexEngine.queryAndCount(
|
const { data: results } = await indexEngine.query<"product">({
|
||||||
{
|
fields: [
|
||||||
select: {
|
"product.*",
|
||||||
product: {
|
"product.variants.*",
|
||||||
variants: {
|
"product.variants.prices.*",
|
||||||
prices: true,
|
],
|
||||||
|
filters: {
|
||||||
|
product: {
|
||||||
|
variants: {
|
||||||
|
prices: {
|
||||||
|
amount: { $gt: 50 },
|
||||||
|
currency_code: { $eq: "AUD" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
where: {
|
},
|
||||||
"product.variants.prices.amount": { $gt: 50 },
|
pagination: {
|
||||||
"product.variants.prices.currency_code": { $eq: "AUD" },
|
order: {
|
||||||
|
product: {
|
||||||
|
variants: {
|
||||||
|
prices: {
|
||||||
|
amount: "DESC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
})
|
||||||
orderBy: [{ "product.variants.prices.amount": "DESC" }],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(count).toBe(1)
|
expect(results.length).toBe(1)
|
||||||
|
|
||||||
const variants = results[0].variants
|
const variants = results[0].variants
|
||||||
|
|
||||||
@@ -179,32 +199,40 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
await setTimeout(5000)
|
await setTimeout(5000)
|
||||||
|
|
||||||
const queryArgs = [
|
const queryArgs = {
|
||||||
{
|
fields: [
|
||||||
select: {
|
"product.*",
|
||||||
product: {
|
"product.variants.*",
|
||||||
variants: {
|
"product.variants.prices.*",
|
||||||
prices: true,
|
],
|
||||||
|
filters: {
|
||||||
|
product: {
|
||||||
|
variants: {
|
||||||
|
prices: {
|
||||||
|
amount: { $gt: 50 },
|
||||||
|
currency_code: { $eq: "AUD" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
where: {
|
},
|
||||||
"product.variants.prices.amount": { $gt: 50 },
|
pagination: {
|
||||||
"product.variants.prices.currency_code": { $eq: "AUD" },
|
order: {
|
||||||
|
product: {
|
||||||
|
variants: {
|
||||||
|
prices: {
|
||||||
|
amount: "DESC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
}
|
||||||
orderBy: [{ "product.variants.prices.amount": "DESC" }],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
await indexEngine.queryAndCount(...queryArgs)
|
await indexEngine.query<"product">(queryArgs)
|
||||||
|
|
||||||
const [results, count, perf] = await indexEngine.queryAndCount(
|
const { data: results, metadata } = await indexEngine.query<"product">(
|
||||||
...queryArgs
|
queryArgs
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(perf)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"downlevelIteration": true // to use ES5 specific tooling
|
"downlevelIteration": true // to use ES5 specific tooling
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src", "./medusa/**/*"],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"./dist/**/*",
|
"./dist/**/*",
|
||||||
"__tests__",
|
"__tests__",
|
||||||
|
|||||||
@@ -323,6 +323,7 @@ export const ModulesDefinition: {
|
|||||||
Modules.EVENT_BUS,
|
Modules.EVENT_BUS,
|
||||||
"logger",
|
"logger",
|
||||||
ContainerRegistrationKeys.REMOTE_QUERY,
|
ContainerRegistrationKeys.REMOTE_QUERY,
|
||||||
|
ContainerRegistrationKeys.QUERY,
|
||||||
],
|
],
|
||||||
defaultModuleDeclaration: {
|
defaultModuleDeclaration: {
|
||||||
scope: MODULE_SCOPE.INTERNAL,
|
scope: MODULE_SCOPE.INTERNAL,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { MedusaModule } from "../medusa-module"
|
import { MedusaModule } from "../medusa-module"
|
||||||
import { FileSystem } from "@medusajs/utils"
|
import { FileSystem, toCamelCase } from "@medusajs/utils"
|
||||||
import { GraphQLSchema } from "graphql/type"
|
import { GraphQLSchema } from "graphql/type"
|
||||||
import { parse, printSchema } from "graphql"
|
import { parse, printSchema } from "graphql"
|
||||||
import { codegen } from "@graphql-codegen/core"
|
import { codegen } from "@graphql-codegen/core"
|
||||||
@@ -21,13 +21,13 @@ function buildEntryPointsTypeMap(
|
|||||||
|
|
||||||
return aliases.flatMap((alias) => {
|
return aliases.flatMap((alias) => {
|
||||||
const names = Array.isArray(alias.name) ? alias.name : [alias.name]
|
const names = Array.isArray(alias.name) ? alias.name : [alias.name]
|
||||||
const entity = alias.args?.["entity"]
|
const entity = alias?.["entity"]
|
||||||
return names.map((aliasItem) => {
|
return names.map((aliasItem) => {
|
||||||
return {
|
return {
|
||||||
entryPoint: aliasItem,
|
entryPoint: aliasItem,
|
||||||
entityType: entity
|
entityType: entity
|
||||||
? schema.includes(`export type ${entity} `)
|
? schema.includes(`export type ${entity} `)
|
||||||
? alias.args?.["entity"]
|
? alias?.["entity"]
|
||||||
: "any"
|
: "any"
|
||||||
: "any",
|
: "any",
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,11 @@ function buildEntryPointsTypeMap(
|
|||||||
|
|
||||||
async function generateTypes({
|
async function generateTypes({
|
||||||
outputDir,
|
outputDir,
|
||||||
|
filename,
|
||||||
config,
|
config,
|
||||||
}: {
|
}: {
|
||||||
outputDir: string
|
outputDir: string
|
||||||
|
filename: string
|
||||||
config: Parameters<typeof codegen>[0]
|
config: Parameters<typeof codegen>[0]
|
||||||
}) {
|
}) {
|
||||||
const fileSystem = new FileSystem(outputDir)
|
const fileSystem = new FileSystem(outputDir)
|
||||||
@@ -49,9 +51,11 @@ async function generateTypes({
|
|||||||
let output = await codegen(config)
|
let output = await codegen(config)
|
||||||
const entryPoints = buildEntryPointsTypeMap(output)
|
const entryPoints = buildEntryPointsTypeMap(output)
|
||||||
|
|
||||||
|
const interfaceName = toCamelCase(filename)
|
||||||
|
|
||||||
const remoteQueryEntryPoints = `
|
const remoteQueryEntryPoints = `
|
||||||
declare module '@medusajs/types' {
|
declare module '@medusajs/types' {
|
||||||
interface RemoteQueryEntryPoints {
|
interface ${interfaceName} {
|
||||||
${entryPoints
|
${entryPoints
|
||||||
.map((entry) => ` ${entry.entryPoint}: ${entry.entityType}`)
|
.map((entry) => ` ${entry.entryPoint}: ${entry.entityType}`)
|
||||||
.join("\n")}
|
.join("\n")}
|
||||||
@@ -60,19 +64,31 @@ ${entryPoints
|
|||||||
|
|
||||||
output += remoteQueryEntryPoints
|
output += remoteQueryEntryPoints
|
||||||
|
|
||||||
await fileSystem.create("remote-query-types.d.ts", output)
|
await fileSystem.create(filename + ".d.ts", output)
|
||||||
await fileSystem.create(
|
|
||||||
"index.d.ts",
|
const doesBarrelExists = await fileSystem.exists("index.d.ts")
|
||||||
"export * as RemoteQueryTypes from './remote-query-types'"
|
if (!doesBarrelExists) {
|
||||||
)
|
await fileSystem.create(
|
||||||
|
"index.d.ts",
|
||||||
|
`export * as ${interfaceName}Types from './${filename}'`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const content = await fileSystem.contents("index.d.ts")
|
||||||
|
if (!content.includes(`${interfaceName}Types`)) {
|
||||||
|
const newContent = `export * as ${interfaceName}Types from './${filename}'\n${content}`
|
||||||
|
await fileSystem.create("index.d.ts", newContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function gqlSchemaToTypes({
|
export async function gqlSchemaToTypes({
|
||||||
schema,
|
schema,
|
||||||
outputDir,
|
outputDir,
|
||||||
|
filename,
|
||||||
}: {
|
}: {
|
||||||
schema: GraphQLSchema
|
schema: GraphQLSchema
|
||||||
outputDir: string
|
outputDir: string
|
||||||
|
filename: string
|
||||||
}) {
|
}) {
|
||||||
const config = {
|
const config = {
|
||||||
documents: [],
|
documents: [],
|
||||||
@@ -98,5 +114,5 @@ export async function gqlSchemaToTypes({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
await generateTypes({ outputDir, config })
|
await generateTypes({ outputDir, filename, config })
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
export type Maybe<T> = T | null
|
||||||
|
export type InputMaybe<T> = Maybe<T>
|
||||||
|
export type Exact<T extends { [key: string]: unknown }> = {
|
||||||
|
[K in keyof T]: T[K]
|
||||||
|
}
|
||||||
|
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
|
||||||
|
[SubKey in K]?: Maybe<T[SubKey]>
|
||||||
|
}
|
||||||
|
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
|
||||||
|
[SubKey in K]: Maybe<T[SubKey]>
|
||||||
|
}
|
||||||
|
export type MakeEmpty<
|
||||||
|
T extends { [key: string]: unknown },
|
||||||
|
K extends keyof T
|
||||||
|
> = { [_ in K]?: never }
|
||||||
|
export type Incremental<T> =
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
[P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never
|
||||||
|
}
|
||||||
|
/** All built-in and custom scalars, mapped to their actual values */
|
||||||
|
export type Scalars = {
|
||||||
|
ID: { input: string; output: string }
|
||||||
|
String: { input: string; output: string }
|
||||||
|
Boolean: { input: boolean; output: boolean }
|
||||||
|
Int: { input: number; output: number }
|
||||||
|
Float: { input: number; output: number }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Product = {
|
||||||
|
__typename?: "Product"
|
||||||
|
id?: Maybe<Scalars["String"]["output"]>
|
||||||
|
title?: Maybe<Scalars["String"]["output"]>
|
||||||
|
variants?: Maybe<Array<Maybe<ProductVariant>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductVariant = {
|
||||||
|
__typename?: "ProductVariant"
|
||||||
|
id?: Maybe<Scalars["String"]["output"]>
|
||||||
|
product_id?: Maybe<Scalars["String"]["output"]>
|
||||||
|
sku?: Maybe<Scalars["String"]["output"]>
|
||||||
|
prices?: Maybe<Array<Maybe<Price>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Price = {
|
||||||
|
__typename?: "Price"
|
||||||
|
amount?: Maybe<Scalars["Int"]["output"]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FixtureEntryPoints {
|
||||||
|
product_variant: ProductVariant
|
||||||
|
product_variants: ProductVariant
|
||||||
|
variant: ProductVariant
|
||||||
|
variants: ProductVariant
|
||||||
|
product: Product
|
||||||
|
products: Product
|
||||||
|
price: Price
|
||||||
|
prices: Price
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "../index-service-entry-points" {
|
||||||
|
interface IndexServiceEntryPoints extends FixtureEntryPoints {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { expectTypeOf } from "expect-type"
|
||||||
|
import "../__fixtures__/index-service-entry-points"
|
||||||
|
import { OperatorMap } from "../operator-map"
|
||||||
|
import { IndexQueryConfig, OrderBy } from "../query-config"
|
||||||
|
|
||||||
|
describe("IndexQueryConfig", () => {
|
||||||
|
it("should infer the config types properly", async () => {
|
||||||
|
type IndexConfig = IndexQueryConfig<"product">
|
||||||
|
|
||||||
|
expectTypeOf<IndexConfig["fields"]>().toEqualTypeOf<
|
||||||
|
(
|
||||||
|
| "id"
|
||||||
|
| "title"
|
||||||
|
| "variants.*"
|
||||||
|
| "variants.id"
|
||||||
|
| "variants.product_id"
|
||||||
|
| "variants.sku"
|
||||||
|
| "variants.prices.*"
|
||||||
|
| "variants.prices.amount"
|
||||||
|
)[]
|
||||||
|
>()
|
||||||
|
|
||||||
|
expectTypeOf<IndexConfig["filters"]>().toEqualTypeOf<
|
||||||
|
| {
|
||||||
|
id?: string | string[] | OperatorMap<string>
|
||||||
|
title?: string | string[] | OperatorMap<string>
|
||||||
|
variants?: {
|
||||||
|
id?: string | string[] | OperatorMap<string>
|
||||||
|
product_id?: string | string[] | OperatorMap<string>
|
||||||
|
sku?: string | string[] | OperatorMap<string>
|
||||||
|
prices?: {
|
||||||
|
amount?: number | number[] | OperatorMap<number>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>()
|
||||||
|
|
||||||
|
expectTypeOf<IndexConfig["pagination"]>().toEqualTypeOf<
|
||||||
|
| {
|
||||||
|
skip?: number
|
||||||
|
take?: number
|
||||||
|
order?: {
|
||||||
|
id?: OrderBy
|
||||||
|
title?: OrderBy
|
||||||
|
variants?: {
|
||||||
|
id?: OrderBy
|
||||||
|
product_id?: OrderBy
|
||||||
|
sku?: OrderBy
|
||||||
|
prices?: {
|
||||||
|
amount?: OrderBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { ModuleJoinerConfig } from "../modules-sdk"
|
||||||
|
|
||||||
|
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 EntityNameModuleConfigMap = {
|
||||||
|
[key: string]: ModuleJoinerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SchemaPropertiesMap = {
|
||||||
|
[key: string]: {
|
||||||
|
shortCutOf?: string
|
||||||
|
ref: SchemaObjectEntityRepresentation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the schema objects representation once the schema has been processed
|
||||||
|
*/
|
||||||
|
export type SchemaObjectRepresentation =
|
||||||
|
| {
|
||||||
|
[key: string]: SchemaObjectEntityRepresentation
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
_schemaPropertiesMap: SchemaPropertiesMap
|
||||||
|
_serviceNameModuleConfigMap: EntityNameModuleConfigMap
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Bucket filled with map of entry point -> types that are autogenerated by the codegen from the config schema
|
||||||
|
*/
|
||||||
|
export interface IndexServiceEntryPoints {}
|
||||||
@@ -1 +1,6 @@
|
|||||||
export * from "./service"
|
export * from "./service"
|
||||||
|
export * from "./index-service-entry-points"
|
||||||
|
export * from "./query-config"
|
||||||
|
export * from "./operator-map"
|
||||||
|
export * from "./common"
|
||||||
|
export * from "./sotrage-provider"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export type OperatorMap<T> = {
|
||||||
|
$eq: T
|
||||||
|
$lt: T
|
||||||
|
$lte: T
|
||||||
|
$gt: T
|
||||||
|
$gte: T
|
||||||
|
$ne: T
|
||||||
|
$in: T
|
||||||
|
$is: T
|
||||||
|
$like: T
|
||||||
|
$ilike: T
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Prettify } from "../../common"
|
||||||
|
|
||||||
|
export type ExcludedProps = "__typename"
|
||||||
|
export type Depth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
export type CleanupObject<T> = Prettify<Omit<Exclude<T, symbol>, ExcludedProps>>
|
||||||
|
export type OmitNever<T extends object> = {
|
||||||
|
[K in keyof T as TypeOnly<T[K]> extends never ? never : K]: T[K]
|
||||||
|
}
|
||||||
|
export type TypeOnly<T> = Required<Exclude<T, null | undefined>>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./query-input-config"
|
||||||
|
export * from "./query-input-config-fields"
|
||||||
|
export * from "./query-input-config-filters"
|
||||||
|
export * from "./query-input-config-order-by"
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { ExcludedProps, TypeOnly } from "./common"
|
||||||
|
|
||||||
|
type Marker = [never, 0, 1, 2, 3, 4]
|
||||||
|
|
||||||
|
type RawBigNumberPrefix = "raw_"
|
||||||
|
|
||||||
|
type ExpandStarSelector<
|
||||||
|
T extends object,
|
||||||
|
Depth extends number,
|
||||||
|
Exclusion extends string[]
|
||||||
|
> = ObjectToIndexFields<T & { "*": "*" }, Depth, Exclusion>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output an array of strings representing the path to each leaf node in an object
|
||||||
|
*/
|
||||||
|
export type ObjectToIndexFields<
|
||||||
|
MaybeT,
|
||||||
|
Depth extends number = 2,
|
||||||
|
Exclusion extends string[] = [],
|
||||||
|
T = TypeOnly<MaybeT>
|
||||||
|
> = Depth extends never
|
||||||
|
? never
|
||||||
|
: T extends object
|
||||||
|
? {
|
||||||
|
[K in keyof T]: K extends // handle big number
|
||||||
|
`${RawBigNumberPrefix}${string}`
|
||||||
|
? Exclude<K, symbol>
|
||||||
|
: // Special props that should be excluded
|
||||||
|
K extends ExcludedProps
|
||||||
|
? never
|
||||||
|
: // Prevent recursive reference to itself
|
||||||
|
K extends Exclusion[number]
|
||||||
|
? never
|
||||||
|
: TypeOnly<T[K]> extends Array<infer R>
|
||||||
|
? TypeOnly<R> extends Date
|
||||||
|
? Exclude<K, symbol>
|
||||||
|
: TypeOnly<R> extends { __typename: any }
|
||||||
|
? `${Exclude<K, symbol>}.${ExpandStarSelector<
|
||||||
|
TypeOnly<R>,
|
||||||
|
Marker[Depth],
|
||||||
|
[K & string, ...Exclusion]
|
||||||
|
>}`
|
||||||
|
: TypeOnly<R> extends object
|
||||||
|
? Exclude<K, symbol>
|
||||||
|
: never
|
||||||
|
: TypeOnly<T[K]> extends Date
|
||||||
|
? Exclude<K, symbol>
|
||||||
|
: TypeOnly<T[K]> extends { __typename: any }
|
||||||
|
? `${Exclude<K, symbol>}.${ExpandStarSelector<
|
||||||
|
TypeOnly<T[K]>,
|
||||||
|
Marker[Depth],
|
||||||
|
[K & string, ...Exclusion]
|
||||||
|
>}`
|
||||||
|
: T[K] extends object
|
||||||
|
? Exclude<K, symbol>
|
||||||
|
: Exclude<K, symbol>
|
||||||
|
}[keyof T]
|
||||||
|
: never
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Prettify } from "../../common"
|
||||||
|
import { IndexServiceEntryPoints } from "../index-service-entry-points"
|
||||||
|
import { OperatorMap } from "../operator-map"
|
||||||
|
import {
|
||||||
|
CleanupObject,
|
||||||
|
Depth,
|
||||||
|
ExcludedProps,
|
||||||
|
OmitNever,
|
||||||
|
TypeOnly,
|
||||||
|
} from "./common"
|
||||||
|
|
||||||
|
type ExtractFiltersOperators<
|
||||||
|
MaybeT,
|
||||||
|
Lim extends number = Depth[2],
|
||||||
|
Exclusion extends string[] = [],
|
||||||
|
T = TypeOnly<MaybeT>
|
||||||
|
> = {
|
||||||
|
[Key in keyof T]?: Key extends Exclusion[number]
|
||||||
|
? never
|
||||||
|
: Key extends ExcludedProps
|
||||||
|
? never
|
||||||
|
: TypeOnly<T[Key]> extends string | number | boolean | Date
|
||||||
|
? TypeOnly<T[Key]> | TypeOnly<T[Key]>[] | OperatorMap<TypeOnly<T[Key]>>
|
||||||
|
: TypeOnly<T[Key]> extends Array<infer R>
|
||||||
|
? TypeOnly<R> extends { __typename: any }
|
||||||
|
? IndexFilters<Key & string, T, [Key & string, ...Exclusion], Depth[Lim]>
|
||||||
|
: TypeOnly<R> extends object
|
||||||
|
? CleanupObject<TypeOnly<R>>
|
||||||
|
: never
|
||||||
|
: TypeOnly<T[Key]> extends { __typename: any }
|
||||||
|
? IndexFilters<
|
||||||
|
Key & string,
|
||||||
|
T[Key],
|
||||||
|
[Key & string, ...Exclusion],
|
||||||
|
Depth[Lim]
|
||||||
|
>
|
||||||
|
: TypeOnly<T[Key]> extends object
|
||||||
|
? CleanupObject<TypeOnly<T[Key]>>
|
||||||
|
: never
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all available filters from an index entry point deeply
|
||||||
|
*/
|
||||||
|
export type IndexFilters<
|
||||||
|
TEntry extends string,
|
||||||
|
IndexEntryPointsLevel = IndexServiceEntryPoints,
|
||||||
|
Exclusion extends string[] = [],
|
||||||
|
Lim extends number = Depth[3]
|
||||||
|
> = Lim extends number
|
||||||
|
? TEntry extends keyof IndexEntryPointsLevel
|
||||||
|
? TypeOnly<IndexEntryPointsLevel[TEntry]> extends Array<infer V>
|
||||||
|
? Prettify<
|
||||||
|
OmitNever<ExtractFiltersOperators<V, Lim, [TEntry, ...Exclusion]>>
|
||||||
|
>
|
||||||
|
: Prettify<
|
||||||
|
OmitNever<
|
||||||
|
ExtractFiltersOperators<
|
||||||
|
IndexEntryPointsLevel[TEntry],
|
||||||
|
Lim,
|
||||||
|
[TEntry, ...Exclusion]
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
: Record<string, any>
|
||||||
|
: never
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Prettify } from "../../common"
|
||||||
|
import { IndexServiceEntryPoints } from "../index-service-entry-points"
|
||||||
|
import {
|
||||||
|
CleanupObject,
|
||||||
|
Depth,
|
||||||
|
ExcludedProps,
|
||||||
|
OmitNever,
|
||||||
|
TypeOnly,
|
||||||
|
} from "./common"
|
||||||
|
|
||||||
|
export type OrderBy = "ASC" | "DESC" | 1 | -1 | true | false
|
||||||
|
|
||||||
|
type ExtractOrderByOperators<
|
||||||
|
MaybeT,
|
||||||
|
Lim extends number = Depth[2],
|
||||||
|
Exclusion extends string[] = [],
|
||||||
|
T = TypeOnly<MaybeT>
|
||||||
|
> = {
|
||||||
|
[Key in keyof T]?: Key extends Exclusion[number]
|
||||||
|
? never
|
||||||
|
: Key extends ExcludedProps
|
||||||
|
? never
|
||||||
|
: TypeOnly<T[Key]> extends string | number | boolean | Date
|
||||||
|
? OrderBy
|
||||||
|
: TypeOnly<T[Key]> extends Array<infer R>
|
||||||
|
? TypeOnly<R> extends { __typename: any }
|
||||||
|
? IndexOrderBy<Key & string, T, [Key & string, ...Exclusion], Depth[Lim]>
|
||||||
|
: TypeOnly<R> extends object
|
||||||
|
? CleanupObject<TypeOnly<R>>
|
||||||
|
: never
|
||||||
|
: TypeOnly<T[Key]> extends { __typename: any }
|
||||||
|
? IndexOrderBy<
|
||||||
|
Key & string,
|
||||||
|
T[Key],
|
||||||
|
[Key & string, ...Exclusion],
|
||||||
|
Depth[Lim]
|
||||||
|
>
|
||||||
|
: TypeOnly<T[Key]> extends object
|
||||||
|
? CleanupObject<TypeOnly<T[Key]>>
|
||||||
|
: never
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all available orderBy from a remote entry point deeply
|
||||||
|
*/
|
||||||
|
export type IndexOrderBy<
|
||||||
|
TEntry extends string,
|
||||||
|
IndexEntryPointsLevel = IndexServiceEntryPoints,
|
||||||
|
Exclusion extends string[] = [],
|
||||||
|
Lim extends number = Depth[3]
|
||||||
|
> = Lim extends number
|
||||||
|
? TEntry extends keyof IndexEntryPointsLevel
|
||||||
|
? TypeOnly<IndexEntryPointsLevel[TEntry]> extends Array<infer V>
|
||||||
|
? Prettify<
|
||||||
|
OmitNever<ExtractOrderByOperators<V, Lim, [TEntry, ...Exclusion]>>
|
||||||
|
>
|
||||||
|
: Prettify<
|
||||||
|
OmitNever<
|
||||||
|
ExtractOrderByOperators<
|
||||||
|
IndexEntryPointsLevel[TEntry],
|
||||||
|
Lim,
|
||||||
|
[TEntry, ...Exclusion]
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
: Record<string, any>
|
||||||
|
: never
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { ObjectToIndexFields } from "./query-input-config-fields"
|
||||||
|
import { IndexFilters } from "./query-input-config-filters"
|
||||||
|
import { IndexOrderBy } from "./query-input-config-order-by"
|
||||||
|
import { IndexServiceEntryPoints } from "../index-service-entry-points"
|
||||||
|
|
||||||
|
export type IndexQueryConfig<TEntry extends string> = {
|
||||||
|
fields: ObjectToIndexFields<
|
||||||
|
IndexServiceEntryPoints[TEntry & keyof IndexServiceEntryPoints]
|
||||||
|
> extends never
|
||||||
|
? string[]
|
||||||
|
: ObjectToIndexFields<
|
||||||
|
IndexServiceEntryPoints[TEntry & keyof IndexServiceEntryPoints]
|
||||||
|
>[]
|
||||||
|
filters?: IndexFilters<TEntry>
|
||||||
|
joinFilters?: IndexFilters<TEntry>
|
||||||
|
pagination?: {
|
||||||
|
skip?: number
|
||||||
|
take?: number
|
||||||
|
order?: IndexOrderBy<TEntry>
|
||||||
|
}
|
||||||
|
keepFilteredEntities?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QueryFunctionReturnPagination = {
|
||||||
|
skip?: number
|
||||||
|
take?: number
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The QueryResultSet presents a typed output for the
|
||||||
|
* result returned by the index search engine, it doesnt narrow down the type
|
||||||
|
* based on the intput fields.
|
||||||
|
*/
|
||||||
|
export type QueryResultSet<TEntry extends string> = {
|
||||||
|
data: TEntry extends keyof IndexServiceEntryPoints
|
||||||
|
? IndexServiceEntryPoints[TEntry][]
|
||||||
|
: any[]
|
||||||
|
metadata?: QueryFunctionReturnPagination
|
||||||
|
}
|
||||||
@@ -1,6 +1,20 @@
|
|||||||
import { IModuleService } from "../modules-sdk"
|
import { IModuleService, ModuleServiceInitializeOptions } from "../modules-sdk"
|
||||||
|
import { IndexQueryConfig, QueryResultSet } from "./query-config"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the module options that can be provided
|
||||||
|
*/
|
||||||
|
export interface IndexModuleOptions {
|
||||||
|
customAdapter?: {
|
||||||
|
constructor: new (...args: any[]) => any
|
||||||
|
options: any
|
||||||
|
}
|
||||||
|
defaultAdapterOptions?: ModuleServiceInitializeOptions
|
||||||
|
schema: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface IIndexService extends IModuleService {
|
export interface IIndexService extends IModuleService {
|
||||||
query(...args): Promise<any>
|
query<const TEntry extends string>(
|
||||||
queryAndCount(...args): Promise<any>
|
config: IndexQueryConfig<TEntry>
|
||||||
|
): Promise<QueryResultSet<TEntry>>
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { IndexQueryConfig, QueryResultSet } from "./query-config"
|
||||||
|
import { Subscriber } from "../event-bus"
|
||||||
|
import { SchemaObjectEntityRepresentation } from "./common"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the storage provider interface,
|
||||||
|
*/
|
||||||
|
export interface StorageProvider {
|
||||||
|
/*new (
|
||||||
|
container: Record<string, any>,
|
||||||
|
options: {
|
||||||
|
schemaObjectRepresentation: SchemaObjectRepresentation
|
||||||
|
entityMap: Record<string, any>
|
||||||
|
},
|
||||||
|
moduleOptions: IndexModuleOptions
|
||||||
|
)*/
|
||||||
|
|
||||||
|
onApplicationStart?(): Promise<void>
|
||||||
|
|
||||||
|
query<const TEntry extends string>(
|
||||||
|
config: IndexQueryConfig<TEntry>
|
||||||
|
): Promise<QueryResultSet<TEntry>>
|
||||||
|
|
||||||
|
consumeEvent(
|
||||||
|
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
|
||||||
|
): Subscriber<any>
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "tsc --watch -p ./tsconfig.build.json",
|
"watch": "tsc --watch -p ./tsconfig.build.json",
|
||||||
"watch:test": "tsc --build tsconfig.spec.json --watch",
|
"watch:test": "tsc --build tsconfig.spec.json --watch",
|
||||||
"build": "rimraf dist && tsc --noEmit && tsc -p ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json",
|
"build": "rimraf dist && tsc -p ./tsconfig.spec.json --noEmit && tsc -p ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json",
|
||||||
"test": "jest --runInBand --bail --passWithNoTests --forceExit -- src",
|
"test": "jest --runInBand --bail --passWithNoTests --forceExit -- src",
|
||||||
"test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.ts"
|
"test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.ts"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ async function start({ port, directory, types }) {
|
|||||||
const outputDirGeneratedTypes = path.join(directory, ".medusa")
|
const outputDirGeneratedTypes = path.join(directory, ".medusa")
|
||||||
await gqlSchemaToTypes({
|
await gqlSchemaToTypes({
|
||||||
outputDir: outputDirGeneratedTypes,
|
outputDir: outputDirGeneratedTypes,
|
||||||
|
filename: "remote-query-entry-points",
|
||||||
schema: gqlSchema,
|
schema: gqlSchema,
|
||||||
})
|
})
|
||||||
logger.info("Geneated modules types")
|
logger.info("Geneated modules types")
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ node_modules
|
|||||||
.env*
|
.env*
|
||||||
.env
|
.env
|
||||||
*.sql
|
*.sql
|
||||||
|
.medusa
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
MedusaAppLoader,
|
|
||||||
configLoader,
|
configLoader,
|
||||||
container,
|
container,
|
||||||
logger,
|
logger,
|
||||||
|
MedusaAppLoader,
|
||||||
} from "@medusajs/framework"
|
} from "@medusajs/framework"
|
||||||
import { MedusaAppOutput, MedusaModule } from "@medusajs/modules-sdk"
|
import { MedusaAppOutput, MedusaModule } from "@medusajs/modules-sdk"
|
||||||
import { EventBusTypes } from "@medusajs/types"
|
import { EventBusTypes, IndexTypes } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
ModuleRegistrationName,
|
ModuleRegistrationName,
|
||||||
@@ -21,7 +21,10 @@ import { EventBusServiceMock } from "../__fixtures__"
|
|||||||
import { dbName } from "../__fixtures__/medusa-config"
|
import { dbName } from "../__fixtures__/medusa-config"
|
||||||
|
|
||||||
const eventBusMock = new EventBusServiceMock()
|
const eventBusMock = new EventBusServiceMock()
|
||||||
const remoteQueryMock = jest.fn()
|
const queryMock = {
|
||||||
|
graph: jest.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
const dbUtils = dbTestUtilFactory()
|
const dbUtils = dbTestUtilFactory()
|
||||||
|
|
||||||
jest.setTimeout(300000)
|
jest.setTimeout(300000)
|
||||||
@@ -34,44 +37,55 @@ const linkId = "link_id_1"
|
|||||||
|
|
||||||
const sendEvents = async (eventDataToEmit) => {
|
const sendEvents = async (eventDataToEmit) => {
|
||||||
let a = 0
|
let a = 0
|
||||||
remoteQueryMock.mockImplementation((query) => {
|
|
||||||
query = query.__value
|
queryMock.graph = jest.fn().mockImplementation((query) => {
|
||||||
if (query.product) {
|
const entity = query.entity
|
||||||
|
if (entity === "product") {
|
||||||
return {
|
return {
|
||||||
id: a++ > 0 ? "aaaa" : productId,
|
data: {
|
||||||
}
|
id: a++ > 0 ? "aaaa" : productId,
|
||||||
} else if (query.product_variant) {
|
|
||||||
return {
|
|
||||||
id: variantId,
|
|
||||||
sku: "aaa test aaa",
|
|
||||||
product: {
|
|
||||||
id: productId,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else if (query.price_set) {
|
} else if (entity === "product_variant") {
|
||||||
return {
|
return {
|
||||||
id: priceSetId,
|
data: {
|
||||||
}
|
id: variantId,
|
||||||
} else if (query.price) {
|
sku: "aaa test aaa",
|
||||||
return {
|
product: {
|
||||||
id: priceId,
|
id: productId,
|
||||||
amount: 100,
|
|
||||||
price_set: [
|
|
||||||
{
|
|
||||||
id: priceSetId,
|
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
}
|
}
|
||||||
} else if (query.product_variant_price_set) {
|
} else if (entity === "price_set") {
|
||||||
return {
|
return {
|
||||||
id: linkId,
|
data: {
|
||||||
variant_id: variantId,
|
id: priceSetId,
|
||||||
price_set_id: priceSetId,
|
},
|
||||||
variant: [
|
}
|
||||||
{
|
} else if (entity === "price") {
|
||||||
id: variantId,
|
return {
|
||||||
},
|
data: {
|
||||||
],
|
id: priceId,
|
||||||
|
amount: 100,
|
||||||
|
price_set: [
|
||||||
|
{
|
||||||
|
id: priceSetId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if (entity === "product_variant_price_set") {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
id: linkId,
|
||||||
|
variant_id: variantId,
|
||||||
|
price_set_id: priceSetId,
|
||||||
|
variant: [
|
||||||
|
{
|
||||||
|
id: variantId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +97,7 @@ const sendEvents = async (eventDataToEmit) => {
|
|||||||
|
|
||||||
let isFirstTime = true
|
let isFirstTime = true
|
||||||
let medusaAppLoader!: MedusaAppLoader
|
let medusaAppLoader!: MedusaAppLoader
|
||||||
|
let index!: IndexTypes.IIndexService
|
||||||
|
|
||||||
const beforeAll_ = async () => {
|
const beforeAll_ = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -94,7 +109,7 @@ const beforeAll_ = async () => {
|
|||||||
|
|
||||||
container.register({
|
container.register({
|
||||||
[ContainerRegistrationKeys.LOGGER]: asValue(logger),
|
[ContainerRegistrationKeys.LOGGER]: asValue(logger),
|
||||||
[ContainerRegistrationKeys.REMOTE_QUERY]: asValue(null),
|
[ContainerRegistrationKeys.QUERY]: asValue(null),
|
||||||
[ContainerRegistrationKeys.PG_CONNECTION]: asValue(dbUtils.pgConnection_),
|
[ContainerRegistrationKeys.PG_CONNECTION]: asValue(dbUtils.pgConnection_),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -112,16 +127,13 @@ const beforeAll_ = async () => {
|
|||||||
// Bootstrap modules
|
// Bootstrap modules
|
||||||
const globalApp = await medusaAppLoader.load()
|
const globalApp = await medusaAppLoader.load()
|
||||||
|
|
||||||
const index = container.resolve(Modules.INDEX)
|
index = container.resolve(Modules.INDEX)
|
||||||
|
|
||||||
// Mock event bus the index module
|
// Mock event bus the index module
|
||||||
;(index as any).eventBusModuleService_ = eventBusMock
|
;(index as any).eventBusModuleService_ = eventBusMock
|
||||||
|
|
||||||
await globalApp.onApplicationStart()
|
await globalApp.onApplicationStart()
|
||||||
|
;(index as any).storageProvider_.query_ = queryMock
|
||||||
jest
|
|
||||||
.spyOn((index as any).storageProvider_, "remoteQuery_")
|
|
||||||
.mockImplementation(remoteQueryMock)
|
|
||||||
|
|
||||||
return globalApp
|
return globalApp
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -236,8 +248,6 @@ describe("IndexModuleService", function () {
|
|||||||
afterEach(afterEach_)
|
afterEach(afterEach_)
|
||||||
|
|
||||||
it("should create the corresponding index entries and index relation entries", async function () {
|
it("should create the corresponding index entries and index relation entries", async function () {
|
||||||
expect(remoteQueryMock).toHaveBeenCalledTimes(6)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate all index entries and index relation entries
|
* Validate all index entries and index relation entries
|
||||||
*/
|
*/
|
||||||
@@ -396,8 +406,6 @@ describe("IndexModuleService", function () {
|
|||||||
afterEach(afterEach_)
|
afterEach(afterEach_)
|
||||||
|
|
||||||
it("should create the corresponding index entries and index relation entries", async function () {
|
it("should create the corresponding index entries and index relation entries", async function () {
|
||||||
expect(remoteQueryMock).toHaveBeenCalledTimes(6)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate all index entries and index relation entries
|
* Validate all index entries and index relation entries
|
||||||
*/
|
*/
|
||||||
@@ -554,35 +562,38 @@ describe("IndexModuleService", function () {
|
|||||||
|
|
||||||
await updateData(manager)
|
await updateData(manager)
|
||||||
|
|
||||||
remoteQueryMock.mockImplementation((query) => {
|
queryMock.graph = jest.fn().mockImplementation((query) => {
|
||||||
query = query.__value
|
const entity = query.entity
|
||||||
if (query.product) {
|
if (entity === "product") {
|
||||||
return {
|
return {
|
||||||
id: productId,
|
data: {
|
||||||
title: "updated Title",
|
id: productId,
|
||||||
|
title: "updated Title",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
} else if (query.product_variant) {
|
} else if (entity === "product_variant") {
|
||||||
return {
|
return {
|
||||||
id: variantId,
|
data: {
|
||||||
sku: "updated sku",
|
id: variantId,
|
||||||
product: [
|
sku: "updated sku",
|
||||||
{
|
product: [
|
||||||
id: productId,
|
{
|
||||||
},
|
id: productId,
|
||||||
],
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
})
|
})
|
||||||
|
|
||||||
await eventBusMock.emit(eventDataToEmit)
|
await eventBusMock.emit(eventDataToEmit)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(afterEach_)
|
afterEach(afterEach_)
|
||||||
|
|
||||||
it("should update the corresponding index entries", async () => {
|
it("should update the corresponding index entries", async () => {
|
||||||
expect(remoteQueryMock).toHaveBeenCalledTimes(4)
|
|
||||||
|
|
||||||
const updatedIndexEntries = await manager.find(IndexData, {})
|
const updatedIndexEntries = await manager.find(IndexData, {})
|
||||||
|
|
||||||
expect(updatedIndexEntries).toHaveLength(2)
|
expect(updatedIndexEntries).toHaveLength(2)
|
||||||
@@ -667,20 +678,24 @@ describe("IndexModuleService", function () {
|
|||||||
medusaApp.sharedContainer!.resolve(ModuleRegistrationName.INDEX) as any
|
medusaApp.sharedContainer!.resolve(ModuleRegistrationName.INDEX) as any
|
||||||
).container_.manager as EntityManager
|
).container_.manager as EntityManager
|
||||||
|
|
||||||
remoteQueryMock.mockImplementation((query) => {
|
queryMock.graph = jest.fn().mockImplementation((query) => {
|
||||||
query = query.__value
|
const entity = query.entity
|
||||||
if (query.product) {
|
if (entity === "product") {
|
||||||
return {
|
return {
|
||||||
id: productId,
|
data: {
|
||||||
|
id: productId,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
} else if (query.product_variant) {
|
} else if (entity === "product_variant") {
|
||||||
return {
|
return {
|
||||||
id: variantId,
|
data: {
|
||||||
product: [
|
id: variantId,
|
||||||
{
|
product: [
|
||||||
id: productId,
|
{
|
||||||
},
|
id: productId,
|
||||||
],
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,8 +708,6 @@ describe("IndexModuleService", function () {
|
|||||||
afterEach(afterEach_)
|
afterEach(afterEach_)
|
||||||
|
|
||||||
it("should consume all deleted events and delete the index entries", async () => {
|
it("should consume all deleted events and delete the index entries", async () => {
|
||||||
expect(remoteQueryMock).toHaveBeenCalledTimes(7)
|
|
||||||
|
|
||||||
const indexEntries = await manager.find(IndexData, {})
|
const indexEntries = await manager.find(IndexData, {})
|
||||||
const indexRelationEntries = await manager.find(IndexRelation, {})
|
const indexRelationEntries = await manager.find(IndexRelation, {})
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
MedusaAppLoader,
|
|
||||||
configLoader,
|
configLoader,
|
||||||
container,
|
container,
|
||||||
logger,
|
logger,
|
||||||
|
MedusaAppLoader,
|
||||||
} from "@medusajs/framework"
|
} from "@medusajs/framework"
|
||||||
import { MedusaAppOutput, MedusaModule } from "@medusajs/modules-sdk"
|
import { MedusaAppOutput, MedusaModule } from "@medusajs/modules-sdk"
|
||||||
import { IndexTypes } from "@medusajs/types"
|
import { IndexTypes } from "@medusajs/types"
|
||||||
@@ -21,7 +21,10 @@ import { EventBusServiceMock } from "../__fixtures__"
|
|||||||
import { dbName } from "../__fixtures__/medusa-config"
|
import { dbName } from "../__fixtures__/medusa-config"
|
||||||
|
|
||||||
const eventBusMock = new EventBusServiceMock()
|
const eventBusMock = new EventBusServiceMock()
|
||||||
const remoteQueryMock = jest.fn()
|
const queryMock = jest.fn().mockReturnValue({
|
||||||
|
graph: jest.fn(),
|
||||||
|
})
|
||||||
|
|
||||||
const dbUtils = dbTestUtilFactory()
|
const dbUtils = dbTestUtilFactory()
|
||||||
|
|
||||||
jest.setTimeout(300000)
|
jest.setTimeout(300000)
|
||||||
@@ -39,7 +42,7 @@ const beforeAll_ = async () => {
|
|||||||
|
|
||||||
container.register({
|
container.register({
|
||||||
[ContainerRegistrationKeys.LOGGER]: asValue(logger),
|
[ContainerRegistrationKeys.LOGGER]: asValue(logger),
|
||||||
[ContainerRegistrationKeys.REMOTE_QUERY]: asValue(null),
|
[ContainerRegistrationKeys.QUERY]: asValue(null),
|
||||||
[ContainerRegistrationKeys.PG_CONNECTION]: asValue(dbUtils.pgConnection_),
|
[ContainerRegistrationKeys.PG_CONNECTION]: asValue(dbUtils.pgConnection_),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -64,9 +67,7 @@ const beforeAll_ = async () => {
|
|||||||
|
|
||||||
await globalApp.onApplicationStart()
|
await globalApp.onApplicationStart()
|
||||||
|
|
||||||
jest
|
;(index as any).storageProvider_.query_ = queryMock
|
||||||
.spyOn((index as any).storageProvider_, "remoteQuery_")
|
|
||||||
.mockImplementation(remoteQueryMock)
|
|
||||||
|
|
||||||
return globalApp
|
return globalApp
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -287,23 +288,20 @@ describe("IndexModuleService query", function () {
|
|||||||
afterEach(afterEach_)
|
afterEach(afterEach_)
|
||||||
|
|
||||||
it("should query all products ordered by sku DESC", async () => {
|
it("should query all products ordered by sku DESC", async () => {
|
||||||
const [result, count] = await module.queryAndCount(
|
const { data } = await module.query({
|
||||||
{
|
fields: ["product.*", "product.variants.*", "product.variants.prices.*"],
|
||||||
select: {
|
pagination: {
|
||||||
|
order: {
|
||||||
product: {
|
product: {
|
||||||
variants: {
|
variants: {
|
||||||
prices: true,
|
sku: "DESC",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
})
|
||||||
orderBy: [{ "product.variants.sku": "DESC" }],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(count).toEqual(2)
|
expect(data).toEqual([
|
||||||
expect(result).toEqual([
|
|
||||||
{
|
{
|
||||||
id: "prod_2",
|
id: "prod_2",
|
||||||
title: "Product 2 title",
|
title: "Product 2 title",
|
||||||
@@ -345,20 +343,18 @@ describe("IndexModuleService query", function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should query products filtering by variant sku", async () => {
|
it("should query products filtering by variant sku", async () => {
|
||||||
const result = await module.query({
|
const { data } = await module.query({
|
||||||
select: {
|
fields: ["product.*", "product.variants.*", "product.variants.prices.*"],
|
||||||
|
filters: {
|
||||||
product: {
|
product: {
|
||||||
variants: {
|
variants: {
|
||||||
prices: true,
|
sku: { $like: "aaa%" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
where: {
|
|
||||||
"product.variants.sku": { $like: "aaa%" },
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(data).toEqual([
|
||||||
{
|
{
|
||||||
id: "prod_1",
|
id: "prod_1",
|
||||||
variants: [
|
variants: [
|
||||||
@@ -378,24 +374,21 @@ describe("IndexModuleService query", function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should query products filtering by price and returning the complete entity", async () => {
|
it("should query products filtering by price and returning the complete entity", async () => {
|
||||||
const [result] = await module.queryAndCount(
|
const { data } = await module.query({
|
||||||
{
|
fields: ["product.*", "product.variants.*", "product.variants.prices.*"],
|
||||||
select: {
|
filters: {
|
||||||
product: {
|
product: {
|
||||||
variants: {
|
variants: {
|
||||||
prices: true,
|
prices: {
|
||||||
|
amount: { $gt: 50 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
where: {
|
|
||||||
"product.variants.prices.amount": { $gt: "50" },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
keepFilteredEntities: true,
|
||||||
keepFilteredEntities: true,
|
})
|
||||||
}
|
|
||||||
)
|
expect(data).toEqual([
|
||||||
expect(result).toEqual([
|
|
||||||
{
|
{
|
||||||
id: "prod_1",
|
id: "prod_1",
|
||||||
variants: [
|
variants: [
|
||||||
@@ -425,18 +418,11 @@ describe("IndexModuleService query", function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should query all products", async () => {
|
it("should query all products", async () => {
|
||||||
const [result, count] = await module.queryAndCount({
|
const { data } = await module.query({
|
||||||
select: {
|
fields: ["product.*", "product.variants.*", "product.variants.prices.*"],
|
||||||
product: {
|
|
||||||
variants: {
|
|
||||||
prices: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(count).toEqual(2)
|
expect(data).toEqual([
|
||||||
expect(result).toEqual([
|
|
||||||
{
|
{
|
||||||
id: "prod_1",
|
id: "prod_1",
|
||||||
variants: [
|
variants: [
|
||||||
@@ -477,23 +463,15 @@ describe("IndexModuleService query", function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should paginate products", async () => {
|
it("should paginate products", async () => {
|
||||||
const result = await module.query(
|
const { data } = await module.query({
|
||||||
{
|
fields: ["product.*", "product.variants.*", "product.variants.prices.*"],
|
||||||
select: {
|
pagination: {
|
||||||
product: {
|
|
||||||
variants: {
|
|
||||||
prices: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
take: 1,
|
take: 1,
|
||||||
skip: 1,
|
skip: 1,
|
||||||
}
|
},
|
||||||
)
|
})
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(data).toEqual([
|
||||||
{
|
{
|
||||||
id: "prod_2",
|
id: "prod_2",
|
||||||
title: "Product 2 title",
|
title: "Product 2 title",
|
||||||
@@ -509,20 +487,18 @@ describe("IndexModuleService query", function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should handle null values on where clause", async () => {
|
it("should handle null values on where clause", async () => {
|
||||||
const result = await module.query({
|
const { data } = await module.query({
|
||||||
select: {
|
fields: ["product.*", "product.variants.*", "product.variants.prices.*"],
|
||||||
|
filters: {
|
||||||
product: {
|
product: {
|
||||||
variants: {
|
variants: {
|
||||||
prices: true,
|
sku: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
where: {
|
|
||||||
"product.variants.sku": null,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(data).toEqual([
|
||||||
{
|
{
|
||||||
id: "prod_2",
|
id: "prod_2",
|
||||||
title: "Product 2 title",
|
title: "Product 2 title",
|
||||||
@@ -538,15 +514,20 @@ describe("IndexModuleService query", function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should query products filtering by deep nested levels", async () => {
|
it("should query products filtering by deep nested levels", async () => {
|
||||||
const [result] = await module.queryAndCount({
|
const { data } = await module.query({
|
||||||
select: {
|
fields: ["product.*"],
|
||||||
product: true,
|
filters: {
|
||||||
},
|
product: {
|
||||||
where: {
|
deep: {
|
||||||
"product.deep.obj.b": 15,
|
obj: {
|
||||||
|
b: 15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result).toEqual([
|
|
||||||
|
expect(data).toEqual([
|
||||||
{
|
{
|
||||||
id: "prod_2",
|
id: "prod_2",
|
||||||
title: "Product 2 title",
|
title: "Product 2 title",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "tsc --build --watch",
|
"watch": "tsc --build --watch",
|
||||||
"watch:test": "tsc --build tsconfig.spec.json --watch",
|
"watch:test": "tsc --build tsconfig.spec.json --watch",
|
||||||
"build": "rimraf dist && tsc --noEmit && tsc -p ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json",
|
"build": "rimraf dist && tsc -p ./tsconfig.spec.json --noEmit && tsc -p ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json",
|
||||||
"test": "jest --passWithNoTests ./src",
|
"test": "jest --passWithNoTests ./src",
|
||||||
"test:integration": "jest --runInBand --forceExit -- integration-tests/**/__tests__/**/*.ts",
|
"test:integration": "jest --runInBand --forceExit -- integration-tests/**/__tests__/**/*.ts",
|
||||||
"migration:generate": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:generate",
|
"migration:generate": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:generate",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Constructor,
|
||||||
IEventBusModuleService,
|
IEventBusModuleService,
|
||||||
IndexTypes,
|
IndexTypes,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
@@ -9,36 +10,32 @@ import {
|
|||||||
MikroOrmBaseRepository as BaseRepository,
|
MikroOrmBaseRepository as BaseRepository,
|
||||||
Modules,
|
Modules,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import {
|
import { schemaObjectRepresentationPropertiesToOmit } from "@types"
|
||||||
IndexModuleOptions,
|
|
||||||
SchemaObjectRepresentation,
|
|
||||||
schemaObjectRepresentationPropertiesToOmit,
|
|
||||||
StorageProvider,
|
|
||||||
} from "@types"
|
|
||||||
import { buildSchemaObjectRepresentation } from "../utils/build-config"
|
import { buildSchemaObjectRepresentation } from "../utils/build-config"
|
||||||
import { defaultSchema } from "../utils/default-schema"
|
import { defaultSchema } from "../utils/default-schema"
|
||||||
|
import { gqlSchemaToTypes } from "../utils/gql-to-types"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
[Modules.EVENT_BUS]: IEventBusModuleService
|
[Modules.EVENT_BUS]: IEventBusModuleService
|
||||||
storageProviderCtr: StorageProvider
|
storageProviderCtr: Constructor<IndexTypes.StorageProvider>
|
||||||
|
[ContainerRegistrationKeys.QUERY]: RemoteQueryFunction
|
||||||
storageProviderCtrOptions: unknown
|
storageProviderCtrOptions: unknown
|
||||||
[ContainerRegistrationKeys.REMOTE_QUERY]: RemoteQueryFunction
|
|
||||||
baseRepository: BaseRepository
|
baseRepository: BaseRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class IndexModuleService implements IndexTypes.IIndexService {
|
export default class IndexModuleService implements IndexTypes.IIndexService {
|
||||||
private readonly container_: InjectedDependencies
|
private readonly container_: InjectedDependencies
|
||||||
private readonly moduleOptions_: IndexModuleOptions
|
private readonly moduleOptions_: IndexTypes.IndexModuleOptions
|
||||||
|
|
||||||
protected readonly eventBusModuleService_: IEventBusModuleService
|
protected readonly eventBusModuleService_: IEventBusModuleService
|
||||||
|
|
||||||
protected schemaObjectRepresentation_: SchemaObjectRepresentation
|
protected schemaObjectRepresentation_: IndexTypes.SchemaObjectRepresentation
|
||||||
protected schemaEntitiesMap_: Record<string, any>
|
protected schemaEntitiesMap_: Record<string, any>
|
||||||
|
|
||||||
protected readonly storageProviderCtr_: StorageProvider
|
protected readonly storageProviderCtr_: Constructor<IndexTypes.StorageProvider>
|
||||||
protected readonly storageProviderCtrOptions_: unknown
|
protected readonly storageProviderCtrOptions_: unknown
|
||||||
|
|
||||||
protected storageProvider_: StorageProvider
|
protected storageProvider_: IndexTypes.StorageProvider
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
container: InjectedDependencies,
|
container: InjectedDependencies,
|
||||||
@@ -46,7 +43,7 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
|||||||
) {
|
) {
|
||||||
this.container_ = container
|
this.container_ = container
|
||||||
this.moduleOptions_ = (moduleDeclaration.options ??
|
this.moduleOptions_ = (moduleDeclaration.options ??
|
||||||
moduleDeclaration) as unknown as IndexModuleOptions
|
moduleDeclaration) as unknown as IndexTypes.IndexModuleOptions
|
||||||
|
|
||||||
const {
|
const {
|
||||||
[Modules.EVENT_BUS]: eventBusModuleService,
|
[Modules.EVENT_BUS]: eventBusModuleService,
|
||||||
@@ -65,10 +62,6 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__joinerConfig() {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
__hooks = {
|
__hooks = {
|
||||||
onApplicationStart(this: IndexModuleService) {
|
onApplicationStart(this: IndexModuleService) {
|
||||||
return this.onApplicationStart_()
|
return this.onApplicationStart_()
|
||||||
@@ -86,31 +79,29 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
|||||||
entityMap: this.schemaEntitiesMap_,
|
entityMap: this.schemaEntitiesMap_,
|
||||||
}),
|
}),
|
||||||
this.moduleOptions_
|
this.moduleOptions_
|
||||||
)
|
) as IndexTypes.StorageProvider
|
||||||
|
|
||||||
this.registerListeners()
|
this.registerListeners()
|
||||||
|
|
||||||
if (this.storageProvider_.onApplicationStart) {
|
if (this.storageProvider_.onApplicationStart) {
|
||||||
await this.storageProvider_.onApplicationStart()
|
await this.storageProvider_.onApplicationStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await gqlSchemaToTypes(this.moduleOptions_.schema ?? defaultSchema)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(...args) {
|
async query<const TEntry extends string>(
|
||||||
return await this.storageProvider_.query.apply(this.storageProvider_, args)
|
config: IndexTypes.IndexQueryConfig<TEntry>
|
||||||
}
|
): Promise<IndexTypes.QueryResultSet<TEntry>> {
|
||||||
|
return await this.storageProvider_.query(config)
|
||||||
async queryAndCount(...args) {
|
|
||||||
return await this.storageProvider_.queryAndCount.apply(
|
|
||||||
this.storageProvider_,
|
|
||||||
args
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected registerListeners() {
|
protected registerListeners() {
|
||||||
const schemaObjectRepresentation = this.schemaObjectRepresentation_ ?? {}
|
const schemaObjectRepresentation = (this.schemaObjectRepresentation_ ??
|
||||||
|
{}) as IndexTypes.SchemaObjectRepresentation
|
||||||
|
|
||||||
for (const [entityName, schemaEntityObjectRepresentation] of Object.entries(
|
for (const [entityName, schemaEntityObjectRepresentation] of Object.entries(
|
||||||
schemaObjectRepresentation
|
schemaObjectRepresentation
|
||||||
@@ -119,7 +110,9 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
schemaEntityObjectRepresentation.listeners.forEach((listener) => {
|
;(
|
||||||
|
schemaEntityObjectRepresentation as IndexTypes.SchemaObjectEntityRepresentation
|
||||||
|
).listeners.forEach((listener) => {
|
||||||
this.eventBusModuleService_.subscribe(
|
this.eventBusModuleService_.subscribe(
|
||||||
listener,
|
listener,
|
||||||
this.storageProvider_.consumeEvent(schemaEntityObjectRepresentation)
|
this.storageProvider_.consumeEvent(schemaEntityObjectRepresentation)
|
||||||
@@ -136,6 +129,7 @@ export default class IndexModuleService implements IndexTypes.IIndexService {
|
|||||||
const [objectRepresentation, entityMap] = buildSchemaObjectRepresentation(
|
const [objectRepresentation, entityMap] = buildSchemaObjectRepresentation(
|
||||||
this.moduleOptions_.schema ?? defaultSchema
|
this.moduleOptions_.schema ?? defaultSchema
|
||||||
)
|
)
|
||||||
|
|
||||||
this.schemaObjectRepresentation_ = objectRepresentation
|
this.schemaObjectRepresentation_ = objectRepresentation
|
||||||
this.schemaEntitiesMap_ = entityMap
|
this.schemaEntitiesMap_ = entityMap
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Context,
|
Context,
|
||||||
Event,
|
Event,
|
||||||
|
IndexTypes,
|
||||||
RemoteQueryFunction,
|
RemoteQueryFunction,
|
||||||
Subscriber,
|
Subscriber,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
@@ -11,27 +12,20 @@ import {
|
|||||||
isDefined,
|
isDefined,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
MikroOrmBaseRepository as BaseRepository,
|
MikroOrmBaseRepository as BaseRepository,
|
||||||
remoteQueryObjectFromString,
|
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { EntityManager, SqlEntityManager } from "@mikro-orm/postgresql"
|
import { EntityManager, SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
import { IndexData, IndexRelation } from "@models"
|
import { IndexData, IndexRelation } from "@models"
|
||||||
import {
|
|
||||||
EntityNameModuleConfigMap,
|
|
||||||
IndexModuleOptions,
|
|
||||||
QueryFormat,
|
|
||||||
QueryOptions,
|
|
||||||
SchemaObjectEntityRepresentation,
|
|
||||||
SchemaObjectRepresentation,
|
|
||||||
} from "@types"
|
|
||||||
import { createPartitions, QueryBuilder } from "../utils"
|
import { createPartitions, QueryBuilder } from "../utils"
|
||||||
|
import { flattenObjectKeys } from "../utils/flatten-object-keys"
|
||||||
|
import { normalizeFieldsSelection } from "../utils/normalize-fields-selection"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
[ContainerRegistrationKeys.REMOTE_QUERY]: RemoteQueryFunction
|
[ContainerRegistrationKeys.QUERY]: RemoteQueryFunction
|
||||||
baseRepository: BaseRepository
|
baseRepository: BaseRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostgresProvider {
|
export class PostgresProvider implements IndexTypes.StorageProvider {
|
||||||
#isReady_: Promise<boolean>
|
#isReady_: Promise<boolean>
|
||||||
|
|
||||||
protected readonly eventActionToMethodMap_ = {
|
protected readonly eventActionToMethodMap_ = {
|
||||||
@@ -43,29 +37,25 @@ export class PostgresProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected container_: InjectedDependencies
|
protected container_: InjectedDependencies
|
||||||
protected readonly schemaObjectRepresentation_: SchemaObjectRepresentation
|
protected readonly schemaObjectRepresentation_: IndexTypes.SchemaObjectRepresentation
|
||||||
protected readonly schemaEntitiesMap_: Record<string, any>
|
protected readonly schemaEntitiesMap_: Record<string, any>
|
||||||
protected readonly moduleOptions_: IndexModuleOptions
|
protected readonly moduleOptions_: IndexTypes.IndexModuleOptions
|
||||||
protected readonly manager_: SqlEntityManager
|
protected readonly manager_: SqlEntityManager
|
||||||
protected readonly remoteQuery_: RemoteQueryFunction
|
protected readonly query_: RemoteQueryFunction
|
||||||
protected baseRepository_: BaseRepository
|
protected baseRepository_: BaseRepository
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{
|
container: InjectedDependencies,
|
||||||
manager,
|
|
||||||
[ContainerRegistrationKeys.REMOTE_QUERY]: remoteQuery,
|
|
||||||
baseRepository,
|
|
||||||
}: InjectedDependencies,
|
|
||||||
options: {
|
options: {
|
||||||
schemaObjectRepresentation: SchemaObjectRepresentation
|
schemaObjectRepresentation: IndexTypes.SchemaObjectRepresentation
|
||||||
entityMap: Record<string, any>
|
entityMap: Record<string, any>
|
||||||
},
|
},
|
||||||
moduleOptions: IndexModuleOptions
|
moduleOptions: IndexTypes.IndexModuleOptions
|
||||||
) {
|
) {
|
||||||
this.manager_ = manager
|
this.manager_ = container.manager
|
||||||
this.remoteQuery_ = remoteQuery
|
this.query_ = container.query
|
||||||
this.moduleOptions_ = moduleOptions
|
this.moduleOptions_ = moduleOptions
|
||||||
this.baseRepository_ = baseRepository
|
this.baseRepository_ = container.baseRepository
|
||||||
|
|
||||||
this.schemaObjectRepresentation_ = options.schemaObjectRepresentation
|
this.schemaObjectRepresentation_ = options.schemaObjectRepresentation
|
||||||
this.schemaEntitiesMap_ = options.entityMap
|
this.schemaEntitiesMap_ = options.entityMap
|
||||||
@@ -122,7 +112,7 @@ export class PostgresProvider {
|
|||||||
TData extends { id: string; [key: string]: unknown }
|
TData extends { id: string; [key: string]: unknown }
|
||||||
>(
|
>(
|
||||||
data: TData | TData[],
|
data: TData | TData[],
|
||||||
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
|
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
|
||||||
) {
|
) {
|
||||||
const data_ = Array.isArray(data) ? data : [data]
|
const data_ = Array.isArray(data) ? data : [data]
|
||||||
|
|
||||||
@@ -194,120 +184,8 @@ export class PostgresProvider {
|
|||||||
return result
|
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(
|
consumeEvent(
|
||||||
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
|
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
|
||||||
): Subscriber<{ id: string }> {
|
): Subscriber<{ id: string }> {
|
||||||
return async (data: Event) => {
|
return async (data: Event) => {
|
||||||
await this.#isReady_
|
await this.#isReady_
|
||||||
@@ -325,17 +203,13 @@ export class PostgresProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { fields, alias } = schemaEntityObjectRepresentation
|
const { fields, alias } = schemaEntityObjectRepresentation
|
||||||
const entityData = await this.remoteQuery_(
|
const { data: entityData } = await this.query_.graph({
|
||||||
remoteQueryObjectFromString({
|
entity: alias,
|
||||||
entryPoint: alias,
|
filters: {
|
||||||
variables: {
|
id: ids,
|
||||||
filters: {
|
},
|
||||||
id: ids,
|
fields: [...new Set(["id", ...fields])],
|
||||||
},
|
})
|
||||||
},
|
|
||||||
fields: [...new Set(["id", ...fields])],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const argument = {
|
const argument = {
|
||||||
entity: schemaEntityObjectRepresentation.entity,
|
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.
|
* Create the index entry and the index relation entry when this event is emitted.
|
||||||
* @param entity
|
* @param entity
|
||||||
@@ -372,7 +331,7 @@ export class PostgresProvider {
|
|||||||
}: {
|
}: {
|
||||||
entity: string
|
entity: string
|
||||||
data: TData | TData[]
|
data: TData | TData[]
|
||||||
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
|
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
|
||||||
},
|
},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
) {
|
) {
|
||||||
@@ -466,7 +425,7 @@ export class PostgresProvider {
|
|||||||
}: {
|
}: {
|
||||||
entity: string
|
entity: string
|
||||||
data: TData | TData[]
|
data: TData | TData[]
|
||||||
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
|
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
|
||||||
},
|
},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
) {
|
) {
|
||||||
@@ -513,7 +472,7 @@ export class PostgresProvider {
|
|||||||
}: {
|
}: {
|
||||||
entity: string
|
entity: string
|
||||||
data: TData | TData[]
|
data: TData | TData[]
|
||||||
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
|
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
|
||||||
},
|
},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
) {
|
) {
|
||||||
@@ -567,7 +526,7 @@ export class PostgresProvider {
|
|||||||
}: {
|
}: {
|
||||||
entity: string
|
entity: string
|
||||||
data: TData | TData[]
|
data: TData | TData[]
|
||||||
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
|
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
|
||||||
},
|
},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
) {
|
) {
|
||||||
@@ -595,7 +554,7 @@ export class PostgresProvider {
|
|||||||
const parentEntityName = (
|
const parentEntityName = (
|
||||||
this.schemaObjectRepresentation_._serviceNameModuleConfigMap[
|
this.schemaObjectRepresentation_._serviceNameModuleConfigMap[
|
||||||
parentServiceName
|
parentServiceName
|
||||||
] as EntityNameModuleConfigMap[0]
|
] as IndexTypes.EntityNameModuleConfigMap[0]
|
||||||
).linkableKeys?.[parentPropertyId]
|
).linkableKeys?.[parentPropertyId]
|
||||||
|
|
||||||
if (!parentEntityName) {
|
if (!parentEntityName) {
|
||||||
@@ -617,7 +576,7 @@ export class PostgresProvider {
|
|||||||
const childEntityName = (
|
const childEntityName = (
|
||||||
this.schemaObjectRepresentation_._serviceNameModuleConfigMap[
|
this.schemaObjectRepresentation_._serviceNameModuleConfigMap[
|
||||||
childServiceName
|
childServiceName
|
||||||
] as EntityNameModuleConfigMap[0]
|
] as IndexTypes.EntityNameModuleConfigMap[0]
|
||||||
).linkableKeys?.[childPropertyId]
|
).linkableKeys?.[childPropertyId]
|
||||||
|
|
||||||
if (!childEntityName) {
|
if (!childEntityName) {
|
||||||
@@ -688,7 +647,7 @@ export class PostgresProvider {
|
|||||||
}: {
|
}: {
|
||||||
entity: string
|
entity: string
|
||||||
data: TData | TData[]
|
data: TData | TData[]
|
||||||
schemaEntityObjectRepresentation: SchemaObjectEntityRepresentation
|
schemaEntityObjectRepresentation: IndexTypes.SchemaObjectEntityRepresentation
|
||||||
},
|
},
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@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 = [
|
export const schemaObjectRepresentationPropertiesToOmit = [
|
||||||
"_schemaPropertiesMap",
|
"_schemaPropertiesMap",
|
||||||
"_serviceNameModuleConfigMap",
|
"_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 = {
|
export type Select = {
|
||||||
[key: string]: boolean | Select | Select[]
|
[key: string]: boolean | Select | Select[]
|
||||||
}
|
}
|
||||||
@@ -152,18 +27,3 @@ export type QueryOptions = {
|
|||||||
orderBy?: OrderBy | OrderBy[]
|
orderBy?: OrderBy | OrderBy[]
|
||||||
keepFilteredEntities?: boolean
|
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,
|
MedusaModule,
|
||||||
} from "@medusajs/modules-sdk"
|
} from "@medusajs/modules-sdk"
|
||||||
import {
|
import {
|
||||||
|
IndexTypes,
|
||||||
JoinerServiceConfigAlias,
|
JoinerServiceConfigAlias,
|
||||||
ModuleJoinerConfig,
|
ModuleJoinerConfig,
|
||||||
ModuleJoinerRelationship,
|
ModuleJoinerRelationship,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { CommonEvents } from "@medusajs/utils"
|
import { CommonEvents } from "@medusajs/utils"
|
||||||
import {
|
import { schemaObjectRepresentationPropertiesToOmit } from "@types"
|
||||||
SchemaObjectEntityRepresentation,
|
|
||||||
SchemaObjectRepresentation,
|
|
||||||
schemaObjectRepresentationPropertiesToOmit,
|
|
||||||
} from "@types"
|
|
||||||
import { Kind, ObjectTypeDefinitionNode } from "graphql/index"
|
import { Kind, ObjectTypeDefinitionNode } from "graphql/index"
|
||||||
|
|
||||||
export const CustomDirectives = {
|
export const CustomDirectives = {
|
||||||
@@ -27,7 +24,7 @@ export const CustomDirectives = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSchemaExecutable(inputSchema: string) {
|
export function makeSchemaExecutable(inputSchema: string) {
|
||||||
const { schema: cleanedSchema } = cleanGraphQLSchema(inputSchema)
|
const { schema: cleanedSchema } = cleanGraphQLSchema(inputSchema)
|
||||||
|
|
||||||
return makeExecutableSchema({ typeDefs: cleanedSchema })
|
return makeExecutableSchema({ typeDefs: cleanedSchema })
|
||||||
@@ -269,7 +266,7 @@ function retrieveLinkModuleAndAlias({
|
|||||||
function getObjectRepresentationRef(
|
function getObjectRepresentationRef(
|
||||||
entityName,
|
entityName,
|
||||||
{ objectRepresentationRef }
|
{ objectRepresentationRef }
|
||||||
): SchemaObjectEntityRepresentation {
|
): IndexTypes.SchemaObjectEntityRepresentation {
|
||||||
return (objectRepresentationRef[entityName] ??= {
|
return (objectRepresentationRef[entityName] ??= {
|
||||||
entity: entityName,
|
entity: entityName,
|
||||||
parents: [],
|
parents: [],
|
||||||
@@ -309,7 +306,7 @@ function processEntity(
|
|||||||
}: {
|
}: {
|
||||||
entitiesMap: any
|
entitiesMap: any
|
||||||
moduleJoinerConfigs: ModuleJoinerConfig[]
|
moduleJoinerConfigs: ModuleJoinerConfig[]
|
||||||
objectRepresentationRef: SchemaObjectRepresentation
|
objectRepresentationRef: IndexTypes.SchemaObjectRepresentation
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
@@ -616,8 +613,11 @@ function processEntity(
|
|||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
function buildAliasMap(objectRepresentation: SchemaObjectRepresentation) {
|
function buildAliasMap(
|
||||||
const aliasMap: SchemaObjectRepresentation["_schemaPropertiesMap"] = {}
|
objectRepresentation: IndexTypes.SchemaObjectRepresentation
|
||||||
|
) {
|
||||||
|
const aliasMap: IndexTypes.SchemaObjectRepresentation["_schemaPropertiesMap"] =
|
||||||
|
{}
|
||||||
|
|
||||||
function recursivelyBuildAliasPath(
|
function recursivelyBuildAliasPath(
|
||||||
current,
|
current,
|
||||||
@@ -705,7 +705,7 @@ function buildAliasMap(objectRepresentation: SchemaObjectRepresentation) {
|
|||||||
*/
|
*/
|
||||||
export function buildSchemaObjectRepresentation(
|
export function buildSchemaObjectRepresentation(
|
||||||
schema
|
schema
|
||||||
): [SchemaObjectRepresentation, Record<string, any>] {
|
): [IndexTypes.SchemaObjectRepresentation, Record<string, any>] {
|
||||||
const moduleJoinerConfigs = MedusaModule.getAllJoinerConfigs()
|
const moduleJoinerConfigs = MedusaModule.getAllJoinerConfigs()
|
||||||
const augmentedSchema = CustomDirectives.Listeners.definition + schema
|
const augmentedSchema = CustomDirectives.Listeners.definition + schema
|
||||||
const executableSchema = makeSchemaExecutable(augmentedSchema)
|
const executableSchema = makeSchemaExecutable(augmentedSchema)
|
||||||
@@ -713,7 +713,7 @@ export function buildSchemaObjectRepresentation(
|
|||||||
|
|
||||||
const objectRepresentation = {
|
const objectRepresentation = {
|
||||||
_serviceNameModuleConfigMap: {},
|
_serviceNameModuleConfigMap: {},
|
||||||
} as SchemaObjectRepresentation
|
} as IndexTypes.SchemaObjectRepresentation
|
||||||
|
|
||||||
Object.entries(entitiesMap).forEach(([entityName, entityMapValue]) => {
|
Object.entries(entitiesMap).forEach(([entityName, entityMapValue]) => {
|
||||||
if (!entityMapValue.astNode) {
|
if (!entityMapValue.astNode) {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
import {
|
import { schemaObjectRepresentationPropertiesToOmit } from "@types"
|
||||||
SchemaObjectRepresentation,
|
import { IndexTypes } from "@medusajs/types"
|
||||||
schemaObjectRepresentationPropertiesToOmit,
|
|
||||||
} from "../types"
|
|
||||||
|
|
||||||
export async function createPartitions(
|
export async function createPartitions(
|
||||||
schemaObjectRepresentation: SchemaObjectRepresentation,
|
schemaObjectRepresentation: IndexTypes.SchemaObjectRepresentation,
|
||||||
manager: SqlEntityManager
|
manager: SqlEntityManager
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const activeSchema = manager.config.get("schema")
|
const activeSchema = manager.config.get("schema")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const defaultSchema = `
|
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
|
id: String
|
||||||
title: String
|
title: String
|
||||||
variants: [ProductVariant]
|
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 { isObject, isString } from "@medusajs/utils"
|
||||||
|
import { IndexTypes } from "@medusajs/types"
|
||||||
import { GraphQLList } from "graphql"
|
import { GraphQLList } from "graphql"
|
||||||
import { Knex } from "knex"
|
import { Knex } from "knex"
|
||||||
import {
|
import { OrderBy, QueryFormat, QueryOptions, Select } from "@types"
|
||||||
OrderBy,
|
|
||||||
QueryFormat,
|
export const OPERATOR_MAP = {
|
||||||
QueryOptions,
|
$eq: "=",
|
||||||
SchemaObjectRepresentation,
|
$lt: "<",
|
||||||
SchemaPropertiesMap,
|
$gt: ">",
|
||||||
Select,
|
$lte: "<=",
|
||||||
} from "../types"
|
$gte: ">=",
|
||||||
|
$ne: "!=",
|
||||||
|
$in: "IN",
|
||||||
|
$is: "IS",
|
||||||
|
$like: "LIKE",
|
||||||
|
$ilike: "ILIKE",
|
||||||
|
}
|
||||||
|
|
||||||
export class QueryBuilder {
|
export class QueryBuilder {
|
||||||
private readonly structure: Select
|
private readonly structure: Select
|
||||||
@@ -16,10 +23,10 @@ export class QueryBuilder {
|
|||||||
private readonly knex: Knex
|
private readonly knex: Knex
|
||||||
private readonly selector: QueryFormat
|
private readonly selector: QueryFormat
|
||||||
private readonly options?: QueryOptions
|
private readonly options?: QueryOptions
|
||||||
private readonly schema: SchemaObjectRepresentation
|
private readonly schema: IndexTypes.SchemaObjectRepresentation
|
||||||
|
|
||||||
constructor(args: {
|
constructor(args: {
|
||||||
schema: SchemaObjectRepresentation
|
schema: IndexTypes.SchemaObjectRepresentation
|
||||||
entityMap: Record<string, any>
|
entityMap: Record<string, any>
|
||||||
knex: Knex
|
knex: Knex
|
||||||
selector: QueryFormat
|
selector: QueryFormat
|
||||||
@@ -37,7 +44,7 @@ export class QueryBuilder {
|
|||||||
return Object.keys(structure ?? {}).filter((key) => key !== "entity")
|
return Object.keys(structure ?? {}).filter((key) => key !== "entity")
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEntity(path): SchemaPropertiesMap[0] {
|
private getEntity(path): IndexTypes.SchemaPropertiesMap[0] {
|
||||||
if (!this.schema._schemaPropertiesMap[path]) {
|
if (!this.schema._schemaPropertiesMap[path]) {
|
||||||
throw new Error(`Could not find entity for path: ${path}`)
|
throw new Error(`Could not find entity for path: ${path}`)
|
||||||
}
|
}
|
||||||
@@ -119,18 +126,6 @@ export class QueryBuilder {
|
|||||||
obj: object,
|
obj: object,
|
||||||
builder: Knex.QueryBuilder
|
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 keys = Object.keys(obj)
|
||||||
|
|
||||||
const getPathAndField = (key: string) => {
|
const getPathAndField = (key: string) => {
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
"src/**/__mocks__",
|
"src/**/__mocks__",
|
||||||
"src/**/__fixtures__",
|
"src/**/__fixtures__",
|
||||||
"node_modules"
|
"node_modules"
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
@@ -27,9 +27,9 @@
|
|||||||
"@types": ["./src/types"]
|
"@types": ["./src/types"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src", "integration-tests"],
|
"include": ["src"],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"dist",
|
"dist",
|
||||||
"node_modules"
|
"node_modules"
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": ["src", "integration-tests"],
|
||||||
|
"exclude": ["node_modules", "dist"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user