chore: Move graphl to a single place (#9303)

* chore: Move graphl to a single place

* add new line
This commit is contained in:
Adrien de Peretti
2024-09-25 12:04:25 +02:00
committed by GitHub
parent 1d3a60023a
commit 358435d715
30 changed files with 78 additions and 62 deletions
@@ -1,358 +0,0 @@
import GraphQLParser from "../../joiner/graphql-ast"
describe("RemoteJoiner.parseQuery", () => {
beforeEach(() => {
jest.clearAllMocks()
})
it("Simple query with fields", async () => {
const graphqlQuery = `
query {
order {
id
number
date
}
}
`
const parser = new GraphQLParser(graphqlQuery)
const rjQuery = parser.parseQuery()
expect(rjQuery).toEqual({
alias: "order",
fields: ["id", "number", "date"],
expands: [],
})
})
it("Simple query with fields and arguments", async () => {
const graphqlQuery = `
query {
order(
id: "ord_123",
another_arg: 987,
complexArg: {
id: "123",
name: "test",
nestedArg: {
nest_id: "abc",
num: 123
}
}
) {
id
number
date
}
}
`
const parser = new GraphQLParser(graphqlQuery)
const rjQuery = parser.parseQuery()
expect(rjQuery).toEqual({
alias: "order",
fields: ["id", "number", "date"],
expands: [],
args: [
{
name: "id",
value: "ord_123",
},
{
name: "another_arg",
value: 987,
},
{
name: "complexArg",
value: {
id: "123",
name: "test",
nestedArg: {
nest_id: "abc",
num: 123,
},
},
},
],
})
})
it("Simple query with mapping fields to services", async () => {
const graphqlQuery = `
query {
order {
id
number
date
products {
product_id
variant_id
order
variant {
name
sku
}
}
}
}
`
const parser = new GraphQLParser(graphqlQuery, {})
const rjQuery = parser.parseQuery()
expect(rjQuery).toEqual({
alias: "order",
fields: ["id", "number", "date", "products"],
expands: [
{
property: "products",
fields: ["product_id", "variant_id", "order", "variant"],
},
{
property: "products.variant",
fields: ["name", "sku"],
},
],
})
})
it("Nested query with fields", async () => {
const graphqlQuery = `
query {
order {
id
number
date
products {
product_id
variant_id
order
variant {
name
sku
}
}
}
}
`
const parser = new GraphQLParser(graphqlQuery)
const rjQuery = parser.parseQuery()
expect(rjQuery).toEqual({
alias: "order",
fields: ["id", "number", "date", "products"],
expands: [
{
property: "products",
fields: ["product_id", "variant_id", "order", "variant"],
},
{
property: "products.variant",
fields: ["name", "sku"],
},
],
})
})
it("Nested query with fields and arguments", async () => {
const graphqlQuery = `
query {
order (order_id: "ord_123") {
id
number
date
products (limit: 10) {
product_id
variant_id
order
variant (complexArg: { id: "123", name: "test", nestedArg: { nest_id: "abc", num: 123 } }, region_id: "reg_123") {
name
sku
}
}
}
}
`
const parser = new GraphQLParser(graphqlQuery)
const rjQuery = parser.parseQuery()
expect(rjQuery).toEqual({
alias: "order",
fields: ["id", "number", "date", "products"],
expands: [
{
property: "products",
fields: ["product_id", "variant_id", "order", "variant"],
args: [
{
name: "limit",
value: 10,
},
],
},
{
property: "products.variant",
fields: ["name", "sku"],
args: [
{
name: "complexArg",
value: {
id: "123",
name: "test",
nestedArg: {
nest_id: "abc",
num: 123,
},
},
},
{
name: "region_id",
value: "reg_123",
},
],
},
],
args: [
{
name: "order_id",
value: "ord_123",
},
],
})
})
it("Nested query with fields and arguments using variables", async () => {
const graphqlQuery = `
query($orderId: ID, $anotherArg: String, $randomVariable: nonValidatedType) {
order (order_id: $orderId, anotherArg: $anotherArg) {
id
number
date
products (randomValue: $randomVariable) {
product_id
variant_id
order
}
}
}
`
const parser = new GraphQLParser(graphqlQuery, {
orderId: 123,
randomVariable: { complex: { num: 12343, str: "str_123" } },
anotherArg: "any string",
})
const rjQuery = parser.parseQuery()
expect(rjQuery).toEqual({
alias: "order",
fields: ["id", "number", "date", "products"],
expands: [
{
property: "products",
fields: ["product_id", "variant_id", "order"],
args: [
{
name: "randomValue",
value: {
complex: {
num: 12343,
str: "str_123",
},
},
},
],
},
],
args: [
{
name: "order_id",
value: 123,
},
{
name: "anotherArg",
value: "any string",
},
],
})
})
it("Nested query with fields and directives", async () => {
const graphqlQuery = `
query {
order(regularArgs: 123) {
id
number @include(if: "date > '2020-01-01'")
date
products {
product_id
variant_id
variant @count {
name @lowerCase
sku @include(if: "name == 'test'")
}
}
}
}
`
const parser = new GraphQLParser(graphqlQuery)
const rjQuery = parser.parseQuery()
expect(rjQuery).toEqual({
alias: "order",
fields: ["id", "number", "date", "products"],
expands: [
{
property: "products",
fields: ["product_id", "variant_id", "variant"],
directives: {
variant: [
{
name: "count",
},
],
},
},
{
property: "products.variant",
fields: ["name", "sku"],
directives: {
name: [
{
name: "lowerCase",
},
],
sku: [
{
name: "include",
args: [
{
name: "if",
value: "name == 'test'",
},
],
},
],
},
},
],
args: [
{
name: "regularArgs",
value: 123,
},
],
directives: {
number: [
{
name: "include",
args: [
{
name: "if",
value: "date > '2020-01-01'",
},
],
},
],
},
})
})
})
@@ -1,194 +0,0 @@
import { RemoteJoinerQuery } from "@medusajs/types"
import {
ArgumentNode,
DirectiveNode,
DocumentNode,
FieldNode,
Kind,
OperationDefinitionNode,
SelectionSetNode,
ValueNode,
parse,
} from "graphql"
interface Argument {
name: string
value?: unknown
}
interface Directive {
name: string
args?: Argument[]
}
interface Entity {
property: string
fields: string[]
args?: Argument[]
directives?: { [field: string]: Directive[] }
}
class GraphQLParser {
private ast: DocumentNode
constructor(input: string, private variables: Record<string, unknown> = {}) {
this.ast = parse(input)
this.variables = variables
}
private parseValueNode(valueNode: ValueNode): unknown {
const obj = {}
switch (valueNode.kind) {
case Kind.VARIABLE:
return this.variables ? this.variables[valueNode.name.value] : undefined
case Kind.INT:
return parseInt(valueNode.value, 10)
case Kind.FLOAT:
return parseFloat(valueNode.value)
case Kind.BOOLEAN:
return Boolean(valueNode.value)
case Kind.STRING:
case Kind.ENUM:
return valueNode.value
case Kind.NULL:
return null
case Kind.LIST:
return valueNode.values.map((v) => this.parseValueNode(v))
case Kind.OBJECT:
for (const field of valueNode.fields) {
obj[field.name.value] = this.parseValueNode(field.value)
}
return obj
default:
return undefined
}
}
private parseArguments(
args: readonly ArgumentNode[]
): Argument[] | undefined {
if (!args.length) {
return
}
return args.map((arg) => {
const value = this.parseValueNode(arg.value)
return {
name: arg.name.value,
value: value,
}
})
}
private parseDirectives(directives: readonly DirectiveNode[]): Directive[] {
return directives.map((directive) => ({
name: directive.name.value,
args: this.parseArguments(directive.arguments || []),
}))
}
private createDirectivesMap(selectionSet: SelectionSetNode):
| {
[field: string]: Directive[]
}
| undefined {
const directivesMap: { [field: string]: Directive[] } = {}
let hasDirectives = false
selectionSet.selections.forEach((field) => {
const fieldName = (field as FieldNode).name.value
const fieldDirectives = this.parseDirectives(
(field as FieldNode).directives || []
)
if (fieldDirectives.length > 0) {
hasDirectives = true
directivesMap[fieldName] = fieldDirectives
}
})
return hasDirectives ? directivesMap : undefined
}
private extractEntities(
node: SelectionSetNode,
parentName = "",
mainService = ""
): Entity[] {
const entities: Entity[] = []
node.selections.forEach((selection) => {
if (selection.kind === "Field") {
const fieldNode = selection as FieldNode
if (!fieldNode.selectionSet) {
return
}
const propName = fieldNode.name.value
const entityName = parentName ? `${parentName}.${propName}` : propName
const nestedEntity: Entity = {
property: entityName.replace(`${mainService}.`, ""),
fields: fieldNode.selectionSet.selections.map(
(field) => (field as FieldNode).name.value
),
args: this.parseArguments(fieldNode.arguments || []),
directives: this.createDirectivesMap(fieldNode.selectionSet),
}
entities.push(nestedEntity)
entities.push(
...this.extractEntities(
fieldNode.selectionSet,
entityName,
mainService
)
)
}
})
return entities
}
public parseQuery(): RemoteJoinerQuery {
const queryDefinition = this.ast.definitions.find(
(definition) => definition.kind === "OperationDefinition"
) as OperationDefinitionNode
if (!queryDefinition) {
throw new Error("No query found")
}
const rootFieldNode = queryDefinition.selectionSet
.selections[0] as FieldNode
const propName = rootFieldNode.name.value
const remoteJoinConfig: RemoteJoinerQuery = {
alias: propName,
fields: [],
expands: [],
}
if (rootFieldNode.arguments) {
remoteJoinConfig.args = this.parseArguments(rootFieldNode.arguments)
}
if (rootFieldNode.selectionSet) {
remoteJoinConfig.fields = rootFieldNode.selectionSet.selections.map(
(field) => (field as FieldNode).name.value
)
remoteJoinConfig.directives = this.createDirectivesMap(
rootFieldNode.selectionSet
)
remoteJoinConfig.expands = this.extractEntities(
rootFieldNode.selectionSet,
propName,
propName
)
}
return remoteJoinConfig
}
}
export default GraphQLParser
@@ -10,12 +10,11 @@ import {
} from "@medusajs/types"
import {
deduplicate,
extractRelationsFromGQL,
GraphQLUtils,
isDefined,
isString,
MedusaError,
} from "@medusajs/utils"
import GraphQLParser from "./graphql-ast"
const BASE_PATH = "_root"
@@ -140,7 +139,7 @@ export class RemoteJoiner {
graphqlQuery: string,
variables?: Record<string, unknown>
): RemoteJoinerQuery {
const parser = new GraphQLParser(graphqlQuery, variables)
const parser = new GraphQLUtils.GraphQLParser(graphqlQuery, variables)
return parser.parseQuery()
}
@@ -154,7 +153,9 @@ export class RemoteJoiner {
) {
this.options.autoCreateServiceNameAlias ??= true
if (this.options.entitiesMap) {
this.entityMap = extractRelationsFromGQL(this.options.entitiesMap)
this.entityMap = GraphQLUtils.extractRelationsFromGQL(
this.options.entitiesMap
)
}
this.buildReferences(