feat(utils): dml to graphql (#8951)
This commit is contained in:
1 parent
419cf1b7d7
commit
5a097d8954
11 files changed
+482
-81
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
import { loadResources } from "../load-internal"
|
|
||||||
import { IModuleService, ModuleResolution } from "@medusajs/types"
|
import { IModuleService, ModuleResolution } from "@medusajs/types"
|
||||||
|
import { upperCaseFirst } from "@medusajs/utils"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import {
|
import {
|
||||||
ModuleWithDmlMixedWithoutJoinerConfigFixtures,
|
ModuleWithDmlMixedWithoutJoinerConfigFixtures,
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
ModuleWithJoinerConfigFixtures,
|
ModuleWithJoinerConfigFixtures,
|
||||||
ModuleWithoutJoinerConfigFixtures,
|
ModuleWithoutJoinerConfigFixtures,
|
||||||
} from "../__fixtures__"
|
} from "../__fixtures__"
|
||||||
import { upperCaseFirst } from "@medusajs/utils"
|
import { loadResources } from "../load-internal"
|
||||||
|
|
||||||
describe("load internal - load resources", () => {
|
describe("load internal - load resources", () => {
|
||||||
describe("when loading the module resources from a path", () => {
|
describe("when loading the module resources from a path", () => {
|
||||||
@@ -64,30 +64,32 @@ describe("load internal - load resources", () => {
|
|||||||
resources.moduleService.prototype as IModuleService
|
resources.moduleService.prototype as IModuleService
|
||||||
).__joinerConfig()
|
).__joinerConfig()
|
||||||
|
|
||||||
expect(generatedJoinerConfig).toEqual({
|
expect(generatedJoinerConfig).toEqual(
|
||||||
serviceName: "module-with-dml-mixed-without-joiner-config",
|
expect.objectContaining({
|
||||||
primaryKeys: ["id"],
|
serviceName: "module-with-dml-mixed-without-joiner-config",
|
||||||
linkableKeys: {
|
primaryKeys: ["id"],
|
||||||
dml_entity_id: "DmlEntity",
|
linkableKeys: {
|
||||||
entity_model_id: "EntityModel",
|
dml_entity_id: "DmlEntity",
|
||||||
},
|
entity_model_id: "EntityModel",
|
||||||
alias: [
|
|
||||||
{
|
|
||||||
name: ["dml_entity", "dml_entities"],
|
|
||||||
args: {
|
|
||||||
entity: "DmlEntity",
|
|
||||||
methodSuffix: "DmlEntities",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
alias: [
|
||||||
name: ["entity_model", "entity_models"],
|
{
|
||||||
args: {
|
name: ["dml_entity", "dml_entities"],
|
||||||
entity: "EntityModel",
|
args: {
|
||||||
methodSuffix: "EntityModels",
|
entity: "DmlEntity",
|
||||||
|
methodSuffix: "DmlEntities",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
],
|
name: ["entity_model", "entity_models"],
|
||||||
})
|
args: {
|
||||||
|
entity: "EntityModel",
|
||||||
|
methodSuffix: "EntityModels",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should return the correct resources and generate the correct joiner config from DML entities", async () => {
|
test("should return the correct resources and generate the correct joiner config from DML entities", async () => {
|
||||||
@@ -143,30 +145,32 @@ describe("load internal - load resources", () => {
|
|||||||
resources.moduleService.prototype as IModuleService
|
resources.moduleService.prototype as IModuleService
|
||||||
).__joinerConfig()
|
).__joinerConfig()
|
||||||
|
|
||||||
expect(generatedJoinerConfig).toEqual({
|
expect(generatedJoinerConfig).toEqual(
|
||||||
serviceName: "module-with-dml-without-joiner-config",
|
expect.objectContaining({
|
||||||
primaryKeys: ["id"],
|
serviceName: "module-with-dml-without-joiner-config",
|
||||||
linkableKeys: {
|
primaryKeys: ["id"],
|
||||||
entity_model_id: "EntityModel",
|
linkableKeys: {
|
||||||
dml_entity_id: "DmlEntity",
|
entity_model_id: "EntityModel",
|
||||||
},
|
dml_entity_id: "DmlEntity",
|
||||||
alias: [
|
|
||||||
{
|
|
||||||
name: ["entity_model", "entity_models"],
|
|
||||||
args: {
|
|
||||||
entity: "EntityModel",
|
|
||||||
methodSuffix: "EntityModels",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
alias: [
|
||||||
name: ["dml_entity", "dml_entities"],
|
{
|
||||||
args: {
|
name: ["entity_model", "entity_models"],
|
||||||
entity: "DmlEntity",
|
args: {
|
||||||
methodSuffix: "DmlEntities",
|
entity: "EntityModel",
|
||||||
|
methodSuffix: "EntityModels",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
],
|
name: ["dml_entity", "dml_entities"],
|
||||||
})
|
args: {
|
||||||
|
entity: "DmlEntity",
|
||||||
|
methodSuffix: "DmlEntities",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should return the correct resources and generate the correct joiner config from mikro orm entities", async () => {
|
test("should return the correct resources and generate the correct joiner config from mikro orm entities", async () => {
|
||||||
@@ -229,6 +233,7 @@ describe("load internal - load resources", () => {
|
|||||||
entity2_id: "Entity2",
|
entity2_id: "Entity2",
|
||||||
entity_model_id: "EntityModel",
|
entity_model_id: "EntityModel",
|
||||||
},
|
},
|
||||||
|
schema: "",
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
name: ["entity2", "entity2s"],
|
name: ["entity2", "entity2s"],
|
||||||
@@ -301,6 +306,7 @@ describe("load internal - load resources", () => {
|
|||||||
serviceName: "module-service",
|
serviceName: "module-service",
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
linkableKeys: {},
|
linkableKeys: {},
|
||||||
|
schema: "",
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
name: ["custom_name"],
|
name: ["custom_name"],
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { model } from "../entity-builder"
|
||||||
|
import { toGraphQLSchema } from "../helpers/create-graphql"
|
||||||
|
|
||||||
|
describe("GraphQL builder", () => {
|
||||||
|
test("define an entity", () => {
|
||||||
|
const tag = model.define("tag", {
|
||||||
|
id: model.id(),
|
||||||
|
value: model.text(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const email = model.define("email", {
|
||||||
|
email: model.text(),
|
||||||
|
isVerified: model.boolean(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const user = model.define("user", {
|
||||||
|
id: model.id(),
|
||||||
|
username: model.text(),
|
||||||
|
email: model.hasOne(() => email, { mappedBy: "owner" }),
|
||||||
|
spend_limit: model.bigNumber(),
|
||||||
|
phones: model.array(),
|
||||||
|
group: model.belongsTo(() => group, { mappedBy: "users" }),
|
||||||
|
role: model.enum(["moderator", "admin", "guest"]).default("guest"),
|
||||||
|
tags: model.manyToMany(() => tag, {
|
||||||
|
pivotTable: "custom_user_tags",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const group = model.define("group", {
|
||||||
|
id: model.number(),
|
||||||
|
name: model.text(),
|
||||||
|
users: model.hasMany(() => user),
|
||||||
|
})
|
||||||
|
|
||||||
|
const toGql = toGraphQLSchema([tag, email, user, group])
|
||||||
|
|
||||||
|
const expected = `
|
||||||
|
type Tag {
|
||||||
|
id: ID!
|
||||||
|
value: String!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type Email {
|
||||||
|
email: String!
|
||||||
|
isVerified: Boolean!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Email {
|
||||||
|
owner: User!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UserRoleEnum {
|
||||||
|
moderator
|
||||||
|
admin
|
||||||
|
guest
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
id: ID!
|
||||||
|
username: String!
|
||||||
|
email: Email!
|
||||||
|
spend_limit: String!
|
||||||
|
phones: [String]!
|
||||||
|
group: [Group]!
|
||||||
|
role: UserRoleEnum!
|
||||||
|
tags: [Tag]!
|
||||||
|
raw_spend_limit: JSON!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group {
|
||||||
|
id: Int!
|
||||||
|
name: String!
|
||||||
|
users: [User]!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
expect(toGql.replace(/\s/g, "")).toEqual(expected.replace(/\s/g, ""))
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -2131,7 +2131,7 @@ describe("Entity builder", () => {
|
|||||||
entity: "Email",
|
entity: "Email",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
mappedBy: "user",
|
mappedBy: "user",
|
||||||
cascade: ["perist", "soft-remove"],
|
cascade: ["persist", "soft-remove"],
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -2284,7 +2284,7 @@ describe("Entity builder", () => {
|
|||||||
entity: "Email",
|
entity: "Email",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
mappedBy: "user",
|
mappedBy: "user",
|
||||||
cascade: ["perist", "soft-remove"],
|
cascade: ["persist", "soft-remove"],
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -2891,7 +2891,7 @@ describe("Entity builder", () => {
|
|||||||
entity: "Email",
|
entity: "Email",
|
||||||
orphanRemoval: true,
|
orphanRemoval: true,
|
||||||
mappedBy: "user",
|
mappedBy: "user",
|
||||||
cascade: ["perist", "soft-remove"],
|
cascade: ["persist", "soft-remove"],
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
@@ -2981,7 +2981,7 @@ describe("Entity builder", () => {
|
|||||||
entity: "Email",
|
entity: "Email",
|
||||||
orphanRemoval: true,
|
orphanRemoval: true,
|
||||||
mappedBy: "user",
|
mappedBy: "user",
|
||||||
cascade: ["perist", "soft-remove"],
|
cascade: ["persist", "soft-remove"],
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
reference: "scalar",
|
reference: "scalar",
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import type { PropertyType } from "@medusajs/types"
|
||||||
|
import { DmlEntity } from "../entity"
|
||||||
|
import { parseEntityName } from "./entity-builder/parse-entity-name"
|
||||||
|
import { getGraphQLAttributeFromDMLPropety } from "./graphql-builder/get-attribute"
|
||||||
|
import { setGraphQLRelationship } from "./graphql-builder/set-relationship"
|
||||||
|
|
||||||
|
export function generateGraphQLFromEntity<T extends DmlEntity<any, any>>(
|
||||||
|
entity: T
|
||||||
|
): string {
|
||||||
|
const { schema } = entity.parse()
|
||||||
|
const { modelName } = parseEntityName(entity)
|
||||||
|
|
||||||
|
let extra: string[] = []
|
||||||
|
let gqlSchema: string[] = []
|
||||||
|
|
||||||
|
Object.entries(schema).forEach(([name, property]) => {
|
||||||
|
const field = property.parse(name)
|
||||||
|
|
||||||
|
if ("fieldName" in field) {
|
||||||
|
const prop = getGraphQLAttributeFromDMLPropety(
|
||||||
|
modelName,
|
||||||
|
name,
|
||||||
|
property as PropertyType<any>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (prop.enum) {
|
||||||
|
extra.push(prop.enum)
|
||||||
|
}
|
||||||
|
|
||||||
|
gqlSchema.push(`${prop.attribute}`)
|
||||||
|
} else {
|
||||||
|
const prop = setGraphQLRelationship(modelName, field)
|
||||||
|
if (prop.extra) {
|
||||||
|
extra.push(prop.extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
gqlSchema.push(`${prop.attribute}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return `
|
||||||
|
${extra.join("\n")}
|
||||||
|
type ${modelName} {
|
||||||
|
${gqlSchema.join("\n")}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a DML entity and returns a GraphQL schema string.
|
||||||
|
* @param entity
|
||||||
|
*/
|
||||||
|
export const toGraphQLSchema = <T extends any[]>(entities: T): string => {
|
||||||
|
const gqlSchemas = entities.map((entity) => {
|
||||||
|
if (DmlEntity.isDmlEntity(entity)) {
|
||||||
|
return generateGraphQLFromEntity(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity
|
||||||
|
})
|
||||||
|
|
||||||
|
return gqlSchemas.join("\n")
|
||||||
|
}
|
||||||
@@ -8,13 +8,13 @@ import type {
|
|||||||
import { Entity, Filter } from "@mikro-orm/core"
|
import { Entity, Filter } from "@mikro-orm/core"
|
||||||
import { mikroOrmSoftDeletableFilterOptions } from "../../dal"
|
import { mikroOrmSoftDeletableFilterOptions } from "../../dal"
|
||||||
import { DmlEntity } from "../entity"
|
import { DmlEntity } from "../entity"
|
||||||
|
import { DuplicateIdPropertyError } from "../errors"
|
||||||
|
import { IdProperty } from "../properties/id"
|
||||||
import { applySearchable } from "./entity-builder/apply-searchable"
|
import { applySearchable } from "./entity-builder/apply-searchable"
|
||||||
import { defineProperty } from "./entity-builder/define-property"
|
import { defineProperty } from "./entity-builder/define-property"
|
||||||
import { defineRelationship } from "./entity-builder/define-relationship"
|
import { defineRelationship } from "./entity-builder/define-relationship"
|
||||||
import { parseEntityName } from "./entity-builder/parse-entity-name"
|
import { parseEntityName } from "./entity-builder/parse-entity-name"
|
||||||
import { applyEntityIndexes, applyIndexes } from "./mikro-orm/apply-indexes"
|
import { applyEntityIndexes, applyIndexes } from "./mikro-orm/apply-indexes"
|
||||||
import { IdProperty } from "../properties/id"
|
|
||||||
import { DuplicateIdPropertyError } from "../errors"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function to create the mikro orm entity builder. The return
|
* Factory function to create the mikro orm entity builder. The return
|
||||||
@@ -36,7 +36,7 @@ export function createMikrORMEntity() {
|
|||||||
* - [team.users] // cannot be an owner
|
* - [team.users] // cannot be an owner
|
||||||
*/
|
*/
|
||||||
// TODO: if we use the util toMikroOrmEntities then a new builder will be used each time, lets think about this. Currently if means that with many to many we need to use the same builder
|
// TODO: if we use the util toMikroOrmEntities then a new builder will be used each time, lets think about this. Currently if means that with many to many we need to use the same builder
|
||||||
const MANY_TO_MANY_TRACKED_REALTIONS: Record<string, boolean> = {}
|
const MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean> = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper function to define a Mikro ORM entity from a
|
* A helper function to define a Mikro ORM entity from a
|
||||||
@@ -61,7 +61,7 @@ export function createMikrORMEntity() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
MANY_TO_MANY_TRACKED_REALTIONS,
|
MANY_TO_MANY_TRACKED_RELATIONS,
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasIdAlreadyDefined = false
|
let hasIdAlreadyDefined = false
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import {
|
|||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
|
OnInit,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
OneToOne,
|
OneToOne,
|
||||||
OnInit,
|
|
||||||
Property,
|
Property,
|
||||||
rel,
|
rel,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
@@ -24,7 +24,7 @@ import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
|
|||||||
import { parseEntityName } from "./parse-entity-name"
|
import { parseEntityName } from "./parse-entity-name"
|
||||||
|
|
||||||
type Context = {
|
type Context = {
|
||||||
MANY_TO_MANY_TRACKED_REALTIONS: Record<string, boolean>
|
MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +43,7 @@ export function defineHasOneRelationship(
|
|||||||
nullable: relationship.nullable,
|
nullable: relationship.nullable,
|
||||||
mappedBy: relationship.mappedBy || camelToSnakeCase(MikroORMEntity.name),
|
mappedBy: relationship.mappedBy || camelToSnakeCase(MikroORMEntity.name),
|
||||||
cascade: shouldRemoveRelated
|
cascade: shouldRemoveRelated
|
||||||
? (["perist", "soft-remove"] as any)
|
? (["persist", "soft-remove"] as any)
|
||||||
: undefined,
|
: undefined,
|
||||||
})(MikroORMEntity.prototype, relationship.name)
|
})(MikroORMEntity.prototype, relationship.name)
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ export function defineHasManyRelationship(
|
|||||||
orphanRemoval: true,
|
orphanRemoval: true,
|
||||||
mappedBy: relationship.mappedBy || camelToSnakeCase(MikroORMEntity.name),
|
mappedBy: relationship.mappedBy || camelToSnakeCase(MikroORMEntity.name),
|
||||||
cascade: shouldRemoveRelated
|
cascade: shouldRemoveRelated
|
||||||
? (["perist", "soft-remove"] as any)
|
? (["persist", "soft-remove"] as any)
|
||||||
: undefined,
|
: undefined,
|
||||||
})(MikroORMEntity.prototype, relationship.name)
|
})(MikroORMEntity.prototype, relationship.name)
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ export function defineManyToManyRelationship(
|
|||||||
relatedModelName,
|
relatedModelName,
|
||||||
pgSchema,
|
pgSchema,
|
||||||
}: { relatedModelName: string; pgSchema: string | undefined },
|
}: { relatedModelName: string; pgSchema: string | undefined },
|
||||||
{ MANY_TO_MANY_TRACKED_REALTIONS }: Context
|
{ MANY_TO_MANY_TRACKED_RELATIONS }: Context
|
||||||
) {
|
) {
|
||||||
let mappedBy = relationship.mappedBy
|
let mappedBy = relationship.mappedBy
|
||||||
let inversedBy: undefined | string
|
let inversedBy: undefined | string
|
||||||
@@ -271,12 +271,12 @@ export function defineManyToManyRelationship(
|
|||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
otherSideRelation.parse(mappedBy).mappedBy &&
|
otherSideRelation.parse(mappedBy).mappedBy &&
|
||||||
MANY_TO_MANY_TRACKED_REALTIONS[`${relatedModelName}.${mappedBy}`]
|
MANY_TO_MANY_TRACKED_RELATIONS[`${relatedModelName}.${mappedBy}`]
|
||||||
) {
|
) {
|
||||||
inversedBy = mappedBy
|
inversedBy = mappedBy
|
||||||
mappedBy = undefined
|
mappedBy = undefined
|
||||||
} else {
|
} else {
|
||||||
MANY_TO_MANY_TRACKED_REALTIONS[
|
MANY_TO_MANY_TRACKED_RELATIONS[
|
||||||
`${MikroORMEntity.name}.${relationship.name}`
|
`${MikroORMEntity.name}.${relationship.name}`
|
||||||
] = true
|
] = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { PropertyType } from "@medusajs/types"
|
||||||
|
import { toPascalCase } from "../../../common"
|
||||||
|
import { PrimaryKeyModifier } from "../../properties/primary-key"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Map of DML data types to GraphQL types
|
||||||
|
*/
|
||||||
|
const GRAPHQL_TYPES = {
|
||||||
|
boolean: "Boolean",
|
||||||
|
dateTime: "DateTime",
|
||||||
|
number: "Int",
|
||||||
|
bigNumber: "String",
|
||||||
|
text: "String",
|
||||||
|
json: "JSON",
|
||||||
|
array: "[String]",
|
||||||
|
id: "ID",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a DML entity schema field as a Mikro ORM property
|
||||||
|
*/
|
||||||
|
export function getGraphQLAttributeFromDMLPropety(
|
||||||
|
modelName: string,
|
||||||
|
propertyName: string,
|
||||||
|
property: PropertyType<any>
|
||||||
|
): {
|
||||||
|
enum?: string
|
||||||
|
attribute: string
|
||||||
|
} {
|
||||||
|
const field = property.parse(propertyName)
|
||||||
|
const fieldType = PrimaryKeyModifier.isPrimaryKeyModifier(property)
|
||||||
|
? "id"
|
||||||
|
: field.dataType.name
|
||||||
|
|
||||||
|
let enumSchema: string | undefined
|
||||||
|
let gqlAttr: {
|
||||||
|
property: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const specialType = {
|
||||||
|
enum: () => {
|
||||||
|
const enumName = toPascalCase(modelName + "_" + field.fieldName + "Enum")
|
||||||
|
const enumValues = field.dataType
|
||||||
|
.options!.choices.map((value) => {
|
||||||
|
return ` ${value}`
|
||||||
|
})
|
||||||
|
.join("\n")
|
||||||
|
|
||||||
|
enumSchema = `enum ${enumName} {\n${enumValues}\n}`
|
||||||
|
gqlAttr = {
|
||||||
|
property: field.fieldName,
|
||||||
|
type: enumName,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: () => {
|
||||||
|
gqlAttr = {
|
||||||
|
property: field.fieldName,
|
||||||
|
type: GRAPHQL_TYPES.id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (specialType[fieldType]) {
|
||||||
|
specialType[fieldType]()
|
||||||
|
} else {
|
||||||
|
gqlAttr = {
|
||||||
|
property: field.fieldName,
|
||||||
|
type: GRAPHQL_TYPES[fieldType] ?? "String",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!field.nullable) {
|
||||||
|
gqlAttr!.type += "!"
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
enum: enumSchema,
|
||||||
|
attribute: `${gqlAttr!.property}: ${gqlAttr!.type}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import {
|
||||||
|
PropertyType,
|
||||||
|
RelationshipMetadata,
|
||||||
|
RelationshipType,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { camelToSnakeCase } from "../../../common"
|
||||||
|
import { DmlEntity } from "../../entity"
|
||||||
|
import { BelongsTo } from "../../relations/belongs-to"
|
||||||
|
import { HasMany } from "../../relations/has-many"
|
||||||
|
import { HasOne } from "../../relations/has-one"
|
||||||
|
import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many"
|
||||||
|
import { parseEntityName } from "../entity-builder/parse-entity-name"
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
MANY_TO_MANY_TRACKED_RELATIONS: Record<string, boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
function defineRelationships(
|
||||||
|
modelName: string,
|
||||||
|
relationship: RelationshipMetadata,
|
||||||
|
relatedEntity: DmlEntity<
|
||||||
|
Record<string, PropertyType<any> | RelationshipType<any>>,
|
||||||
|
any
|
||||||
|
>,
|
||||||
|
{ relatedModelName }: { relatedModelName: string }
|
||||||
|
) {
|
||||||
|
let extra: string | undefined
|
||||||
|
const fieldName = relationship.name
|
||||||
|
|
||||||
|
const mappedBy = relationship.mappedBy || camelToSnakeCase(modelName)
|
||||||
|
const { schema: relationSchema } = relatedEntity.parse()
|
||||||
|
|
||||||
|
const otherSideRelation = relationSchema[mappedBy]
|
||||||
|
|
||||||
|
if (relationship.options?.mappedBy && HasOne.isHasOne(relationship)) {
|
||||||
|
const otherSideFieldName = relationship.options.mappedBy
|
||||||
|
extra = `extend type ${relatedModelName} {\n ${otherSideFieldName}: ${modelName}!\n}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let isArray = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Otherside is a has many. Hence we should defined a ManyToOne
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
HasMany.isHasMany(otherSideRelation) ||
|
||||||
|
DmlManyToMany.isManyToMany(relationship) ||
|
||||||
|
(BelongsTo.isBelongsTo(otherSideRelation) &&
|
||||||
|
HasMany.isHasMany(relationship))
|
||||||
|
) {
|
||||||
|
isArray = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
attribute:
|
||||||
|
`${fieldName}: ${isArray ? "[" : ""}${relatedModelName}${
|
||||||
|
isArray ? "]" : ""
|
||||||
|
}` + (relationship.nullable ? "" : "!"),
|
||||||
|
extra,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setGraphQLRelationship(
|
||||||
|
entityName: string,
|
||||||
|
relationship: RelationshipMetadata
|
||||||
|
): {
|
||||||
|
extra?: string
|
||||||
|
attribute: string
|
||||||
|
} {
|
||||||
|
const relatedEntity =
|
||||||
|
typeof relationship.entity === "function"
|
||||||
|
? (relationship.entity() as unknown)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (!relatedEntity) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid relationship reference for "${entityName}.${relationship.name}". Make sure to define the relationship using a factory function`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DmlEntity.isDmlEntity(relatedEntity)) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid relationship reference for "${entityName}.${relationship.name}". Make sure to return a DML entity from the relationship callback`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { modelName, tableName, pgSchema } = parseEntityName(relatedEntity)
|
||||||
|
const relatedEntityInfo = {
|
||||||
|
relatedModelName: modelName,
|
||||||
|
relatedTableName: tableName,
|
||||||
|
pgSchema,
|
||||||
|
}
|
||||||
|
|
||||||
|
return defineRelationships(
|
||||||
|
entityName,
|
||||||
|
relationship,
|
||||||
|
relatedEntity,
|
||||||
|
relatedEntityInfo
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,13 +1,6 @@
|
|||||||
import {
|
|
||||||
buildLinkableKeysFromDmlObjects,
|
|
||||||
buildLinkableKeysFromMikroOrmObjects,
|
|
||||||
buildLinkConfigFromLinkableKeys,
|
|
||||||
buildLinkConfigFromModelObjects,
|
|
||||||
defineJoinerConfig,
|
|
||||||
} from "../joiner-config-builder"
|
|
||||||
import { Modules } from "../definition"
|
|
||||||
import { model } from "../../dml"
|
|
||||||
import { expectTypeOf } from "expect-type"
|
import { expectTypeOf } from "expect-type"
|
||||||
|
import { upperCaseFirst } from "../../common"
|
||||||
|
import { model } from "../../dml"
|
||||||
import {
|
import {
|
||||||
dmlFulfillment,
|
dmlFulfillment,
|
||||||
dmlFulfillmentProvider,
|
dmlFulfillmentProvider,
|
||||||
@@ -26,7 +19,14 @@ import {
|
|||||||
ShippingOptionRule,
|
ShippingOptionRule,
|
||||||
ShippingProfile,
|
ShippingProfile,
|
||||||
} from "../__fixtures__/joiner-config/entities"
|
} from "../__fixtures__/joiner-config/entities"
|
||||||
import { upperCaseFirst } from "../../common"
|
import { Modules } from "../definition"
|
||||||
|
import {
|
||||||
|
buildLinkableKeysFromDmlObjects,
|
||||||
|
buildLinkableKeysFromMikroOrmObjects,
|
||||||
|
buildLinkConfigFromLinkableKeys,
|
||||||
|
buildLinkConfigFromModelObjects,
|
||||||
|
defineJoinerConfig,
|
||||||
|
} from "../joiner-config-builder"
|
||||||
|
|
||||||
describe("joiner-config-builder", () => {
|
describe("joiner-config-builder", () => {
|
||||||
describe("defineJoiner | Mikro orm objects", () => {
|
describe("defineJoiner | Mikro orm objects", () => {
|
||||||
@@ -47,7 +47,7 @@ describe("joiner-config-builder", () => {
|
|||||||
expect(joinerConfig).toEqual({
|
expect(joinerConfig).toEqual({
|
||||||
serviceName: Modules.FULFILLMENT,
|
serviceName: Modules.FULFILLMENT,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
schema: undefined,
|
schema: "",
|
||||||
linkableKeys: {
|
linkableKeys: {
|
||||||
fulfillment_set_id: FulfillmentSet.name,
|
fulfillment_set_id: FulfillmentSet.name,
|
||||||
shipping_option_id: ShippingOption.name,
|
shipping_option_id: ShippingOption.name,
|
||||||
@@ -135,7 +135,7 @@ describe("joiner-config-builder", () => {
|
|||||||
expect(joinerConfig).toEqual({
|
expect(joinerConfig).toEqual({
|
||||||
serviceName: Modules.FULFILLMENT,
|
serviceName: Modules.FULFILLMENT,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
schema: undefined,
|
schema: "",
|
||||||
linkableKeys: {},
|
linkableKeys: {},
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
@@ -175,7 +175,7 @@ describe("joiner-config-builder", () => {
|
|||||||
expect(joinerConfig).toEqual({
|
expect(joinerConfig).toEqual({
|
||||||
serviceName: Modules.FULFILLMENT,
|
serviceName: Modules.FULFILLMENT,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
schema: undefined,
|
schema: "",
|
||||||
linkableKeys: {
|
linkableKeys: {
|
||||||
fulfillment_set_id: FulfillmentSet.name,
|
fulfillment_set_id: FulfillmentSet.name,
|
||||||
shipping_option_id: ShippingOption.name,
|
shipping_option_id: ShippingOption.name,
|
||||||
@@ -269,7 +269,7 @@ describe("joiner-config-builder", () => {
|
|||||||
expect(joinerConfig).toEqual({
|
expect(joinerConfig).toEqual({
|
||||||
serviceName: Modules.FULFILLMENT,
|
serviceName: Modules.FULFILLMENT,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
schema: undefined,
|
schema: "",
|
||||||
linkableKeys: {},
|
linkableKeys: {},
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
@@ -300,7 +300,7 @@ describe("joiner-config-builder", () => {
|
|||||||
expect(joinerConfig).toEqual({
|
expect(joinerConfig).toEqual({
|
||||||
serviceName: Modules.FULFILLMENT,
|
serviceName: Modules.FULFILLMENT,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
schema: undefined,
|
schema: "",
|
||||||
linkableKeys: {
|
linkableKeys: {
|
||||||
fulfillment_set_id: FulfillmentSet.name,
|
fulfillment_set_id: FulfillmentSet.name,
|
||||||
},
|
},
|
||||||
@@ -335,7 +335,7 @@ describe("joiner-config-builder", () => {
|
|||||||
expect(joinerConfig).toEqual({
|
expect(joinerConfig).toEqual({
|
||||||
serviceName: Modules.FULFILLMENT,
|
serviceName: Modules.FULFILLMENT,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
schema: undefined,
|
schema: expect.any(String),
|
||||||
linkableKeys: {
|
linkableKeys: {
|
||||||
fulfillment_set_id: FulfillmentSet.name,
|
fulfillment_set_id: FulfillmentSet.name,
|
||||||
shipping_option_id: ShippingOption.name,
|
shipping_option_id: ShippingOption.name,
|
||||||
@@ -405,6 +405,59 @@ describe("joiner-config-builder", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const schemaExpected = `type FulfillmentSet {
|
||||||
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
type ShippingOption {
|
||||||
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
type ShippingProfile {
|
||||||
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
type Fulfillment {
|
||||||
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
type FulfillmentProvider {
|
||||||
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
type ServiceZone {
|
||||||
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
type GeoZone {
|
||||||
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}
|
||||||
|
type ShippingOptionRule {
|
||||||
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
deleted_at: DateTime
|
||||||
|
}`
|
||||||
|
|
||||||
|
expect(joinerConfig.schema!.replace(/\s/g, "")).toEqual(
|
||||||
|
schemaExpected.replace(/\s/g, "")
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,25 +4,26 @@ import {
|
|||||||
ModuleJoinerConfig,
|
ModuleJoinerConfig,
|
||||||
PropertyType,
|
PropertyType,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
import { accessSync } from "fs"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { dirname, join, normalize } from "path"
|
import { dirname, join, normalize } from "path"
|
||||||
import {
|
import {
|
||||||
|
MapToConfig,
|
||||||
camelToSnakeCase,
|
camelToSnakeCase,
|
||||||
deduplicate,
|
deduplicate,
|
||||||
getCallerFilePath,
|
getCallerFilePath,
|
||||||
isObject,
|
isObject,
|
||||||
lowerCaseFirst,
|
lowerCaseFirst,
|
||||||
MapToConfig,
|
|
||||||
pluralize,
|
pluralize,
|
||||||
toCamelCase,
|
toCamelCase,
|
||||||
upperCaseFirst,
|
upperCaseFirst,
|
||||||
} from "../common"
|
} from "../common"
|
||||||
import { loadModels } from "./loaders/load-models"
|
|
||||||
import { DmlEntity } from "../dml"
|
import { DmlEntity } from "../dml"
|
||||||
import { BaseRelationship } from "../dml/relations/base"
|
import { toGraphQLSchema } from "../dml/helpers/create-graphql"
|
||||||
import { PrimaryKeyModifier } from "../dml/properties/primary-key"
|
import { PrimaryKeyModifier } from "../dml/properties/primary-key"
|
||||||
|
import { BaseRelationship } from "../dml/relations/base"
|
||||||
|
import { loadModels } from "./loaders/load-models"
|
||||||
import { InferLinkableKeys, InfersLinksConfig } from "./types/links-config"
|
import { InferLinkableKeys, InfersLinksConfig } from "./types/links-config"
|
||||||
import { accessSync } from "fs"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define joiner config for a module based on the models (object representation or entities) present in the models directory. This action will be sync until
|
* Define joiner config for a module based on the models (object representation or entities) present in the models directory. This action will be sync until
|
||||||
@@ -146,6 +147,10 @@ export function defineJoinerConfig(
|
|||||||
deduplicatedLoadedModels.push(model)
|
deduplicatedLoadedModels.push(model)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!schema) {
|
||||||
|
schema = toGraphQLSchema([...modelDefinitions.values()])
|
||||||
|
}
|
||||||
|
|
||||||
if (!linkableKeys) {
|
if (!linkableKeys) {
|
||||||
const linkableKeysFromDml = buildLinkableKeysFromDmlObjects([
|
const linkableKeysFromDml = buildLinkableKeysFromDmlObjects([
|
||||||
...modelDefinitions.values(),
|
...modelDefinitions.values(),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { defineJoinerConfig, Modules } from "@medusajs/utils"
|
import { defineJoinerConfig, Modules } from "@medusajs/utils"
|
||||||
|
import { default as schema } from "./schema"
|
||||||
|
|
||||||
export const joinerConfig = defineJoinerConfig(Modules.INVENTORY, {
|
export const joinerConfig = defineJoinerConfig(Modules.INVENTORY, {
|
||||||
|
schema,
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
name: ["inventory_items", "inventory_item", "inventory"],
|
name: ["inventory_items", "inventory_item", "inventory"],
|
||||||
|
|||||||
Reference in new issue
Block a user