feat(*): Modules export entities and fields (#5242)
This commit is contained in:
1 parent
eeceec791c
commit
130cbc1f43
27 files changed
+801
-226
No files matched your search
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/stock-location": minor
|
||||||
|
"@medusajs/link-modules": minor
|
||||||
|
"@medusajs/modules-sdk": minor
|
||||||
|
"@medusajs/inventory": minor
|
||||||
|
"@medusajs/pricing": minor
|
||||||
|
"@medusajs/product": minor
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Modules exporting schema with entities and fields
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Modules } from "@medusajs/modules-sdk"
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
|
import { InventoryItem, InventoryLevel, ReservationItem } from "./models"
|
||||||
|
|
||||||
export const joinerConfig: ModuleJoinerConfig = {
|
export const joinerConfig: ModuleJoinerConfig = {
|
||||||
serviceName: Modules.INVENTORY,
|
serviceName: Modules.INVENTORY,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
linkableKeys: [
|
linkableKeys: {
|
||||||
"inventory_item_id",
|
inventory_item_id: InventoryItem.name,
|
||||||
"inventory_level_id",
|
inventory_level_id: InventoryLevel.name,
|
||||||
"reservation_item_id",
|
reservation_item_id: ReservationItem.name,
|
||||||
],
|
},
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
name: "inventory_items",
|
name: "inventory_items",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
import * as linkDefinitions from "../definitions"
|
import * as linkDefinitions from "../definitions"
|
||||||
import { getMigration } from "../migration"
|
import { getMigration } from "../migration"
|
||||||
import { InitializeModuleInjectableDependencies } from "../types"
|
import { InitializeModuleInjectableDependencies } from "../types"
|
||||||
import { composeLinkName } from "../utils"
|
import { composeLinkName, generateGraphQLSchema } from "../utils"
|
||||||
import { getLinkModuleDefinition } from "./module-definition"
|
import { getLinkModuleDefinition } from "./module-definition"
|
||||||
|
|
||||||
export const initialize = async (
|
export const initialize = async (
|
||||||
@@ -98,6 +98,8 @@ export const initialize = async (
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
definition.schema = generateGraphQLSchema(definition, primary, foreign)
|
||||||
|
|
||||||
const moduleDefinition = getLinkModuleDefinition(
|
const moduleDefinition = getLinkModuleDefinition(
|
||||||
definition,
|
definition,
|
||||||
primary,
|
primary,
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { MedusaModule } from "@medusajs/modules-sdk"
|
||||||
|
import { ModuleJoinerConfig, ModuleJoinerRelationship } from "@medusajs/types"
|
||||||
|
import { camelToSnakeCase, lowerCaseFirst, toPascalCase } from "@medusajs/utils"
|
||||||
|
import { composeTableName } from "./compose-link-name"
|
||||||
|
|
||||||
|
export function generateGraphQLSchema(
|
||||||
|
joinerConfig: ModuleJoinerConfig,
|
||||||
|
primary: ModuleJoinerRelationship,
|
||||||
|
foreign: ModuleJoinerRelationship
|
||||||
|
) {
|
||||||
|
const fieldNames = primary.foreignKey.split(",").concat(foreign.foreignKey)
|
||||||
|
|
||||||
|
const entityName = toPascalCase(
|
||||||
|
"Link_" +
|
||||||
|
(joinerConfig.databaseConfig?.tableName ??
|
||||||
|
composeTableName(
|
||||||
|
primary.serviceName,
|
||||||
|
primary.foreignKey,
|
||||||
|
foreign.serviceName,
|
||||||
|
foreign.foreignKey
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pivot table fields
|
||||||
|
const fields = fieldNames.reduce((acc, curr) => {
|
||||||
|
acc[curr] = {
|
||||||
|
type: "String",
|
||||||
|
nullable: false,
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
const extraFields = joinerConfig.databaseConfig?.extraFields ?? {}
|
||||||
|
|
||||||
|
for (const column in extraFields) {
|
||||||
|
fields[column] = {
|
||||||
|
type: getGraphQLType(extraFields[column].type),
|
||||||
|
nullable: !!extraFields[column].nullable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link table relationships
|
||||||
|
const primaryField = `${camelToSnakeCase(primary.alias)}: ${toPascalCase(
|
||||||
|
composeTableName(primary.serviceName)
|
||||||
|
)}`
|
||||||
|
|
||||||
|
const foreignField = `${camelToSnakeCase(foreign.alias)}: ${toPascalCase(
|
||||||
|
composeTableName(foreign.serviceName)
|
||||||
|
)}`
|
||||||
|
|
||||||
|
let typeDef = `
|
||||||
|
type ${entityName} {
|
||||||
|
${(Object.entries(fields) as any)
|
||||||
|
.map(
|
||||||
|
([field, { type, nullable }]) =>
|
||||||
|
`${field}: ${nullable ? type : `${type}!`}`
|
||||||
|
)
|
||||||
|
.join("\n ")}
|
||||||
|
|
||||||
|
${primaryField}
|
||||||
|
${foreignField}
|
||||||
|
|
||||||
|
createdAt: String!
|
||||||
|
updatedAt: String!
|
||||||
|
deletedAt: String
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
for (const extend of joinerConfig.extends ?? []) {
|
||||||
|
const extendedModule = MedusaModule.getModuleInstance(extend.serviceName)
|
||||||
|
if (!extendedModule && !extend.relationship.isInternalService) {
|
||||||
|
throw new Error(
|
||||||
|
`Module ${extend.serviceName} not found. Please verify that the module is configured and installed, also the module must be loaded before the link modules.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinerConfig = MedusaModule.getJoinerConfig(extend.serviceName)
|
||||||
|
let extendedEntityName =
|
||||||
|
joinerConfig?.linkableKeys?.[extend.relationship.primaryKey]!
|
||||||
|
|
||||||
|
if (!extendedEntityName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
extendedEntityName = toPascalCase(extendedEntityName)
|
||||||
|
|
||||||
|
const linkTableFieldName = camelToSnakeCase(
|
||||||
|
lowerCaseFirst(extend.relationship.alias)
|
||||||
|
)
|
||||||
|
const type = extend.relationship.isList ? `[${entityName}]` : entityName
|
||||||
|
|
||||||
|
typeDef += `
|
||||||
|
extend type ${extendedEntityName} {
|
||||||
|
${linkTableFieldName}: ${type}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeDef
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGraphQLType(type) {
|
||||||
|
const typeDef = {
|
||||||
|
numeric: "Float",
|
||||||
|
integer: "Int",
|
||||||
|
smallint: "Int",
|
||||||
|
tinyint: "Int",
|
||||||
|
mediumint: "Int",
|
||||||
|
float: "Float",
|
||||||
|
double: "Float",
|
||||||
|
boolean: "Boolean",
|
||||||
|
decimal: "Float",
|
||||||
|
string: "String",
|
||||||
|
uuid: "ID",
|
||||||
|
text: "String",
|
||||||
|
date: "Date",
|
||||||
|
time: "Time",
|
||||||
|
datetime: "DateTime",
|
||||||
|
bigint: "BigInt",
|
||||||
|
blob: "Blob",
|
||||||
|
uint8array: "[Int]",
|
||||||
|
array: "[String]",
|
||||||
|
enumArray: "[String]",
|
||||||
|
enum: "String",
|
||||||
|
json: "JSON",
|
||||||
|
jsonb: "JSON",
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeDef[type] ?? "String"
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { MODULE_RESOURCE_TYPE } from "@medusajs/types"
|
|||||||
|
|
||||||
export * from "./compose-link-name"
|
export * from "./compose-link-name"
|
||||||
export * from "./generate-entity"
|
export * from "./generate-entity"
|
||||||
|
export * from "./generate-schema"
|
||||||
|
|
||||||
export function shouldForceTransaction(target: any): boolean {
|
export function shouldForceTransaction(target: any): boolean {
|
||||||
return target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
return target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
||||||
|
|||||||
@@ -1,77 +1,5 @@
|
|||||||
import { Modules } from "@medusajs/modules-sdk"
|
import * as joinerConfigs from "./joiner-configs"
|
||||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
|
||||||
|
|
||||||
export const joinerConfig: ModuleJoinerConfig[] = [
|
export const joinerConfig = Object.values(joinerConfigs).map(
|
||||||
{
|
(config) => config.default
|
||||||
serviceName: "cartService",
|
)
|
||||||
primaryKeys: ["id"],
|
|
||||||
linkableKeys: ["cart_id"],
|
|
||||||
alias: [
|
|
||||||
{
|
|
||||||
name: "cart",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
relationships: [
|
|
||||||
{
|
|
||||||
serviceName: Modules.PRODUCT,
|
|
||||||
primaryKey: "id",
|
|
||||||
foreignKey: "variant_id",
|
|
||||||
alias: "variant",
|
|
||||||
args: {
|
|
||||||
methodSuffix: "Variants",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
serviceName: "regionService",
|
|
||||||
primaryKey: "id",
|
|
||||||
foreignKey: "region_id",
|
|
||||||
alias: "region",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
serviceName: "customerService",
|
|
||||||
primaryKey: "id",
|
|
||||||
foreignKey: "customer_id",
|
|
||||||
alias: "customer",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
serviceName: "shippingProfileService",
|
|
||||||
primaryKeys: ["id"],
|
|
||||||
linkableKeys: ["profile_id"],
|
|
||||||
alias: [
|
|
||||||
{
|
|
||||||
name: "shipping_profile",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "shipping_profiles",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
serviceName: "regionService",
|
|
||||||
primaryKeys: ["id"],
|
|
||||||
linkableKeys: ["region_id"],
|
|
||||||
alias: [
|
|
||||||
{
|
|
||||||
name: "region",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "regions",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
serviceName: "customerService",
|
|
||||||
primaryKeys: ["id"],
|
|
||||||
linkableKeys: ["customer_id"],
|
|
||||||
alias: [
|
|
||||||
{
|
|
||||||
name: "customer",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "customers",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
serviceName: "cartService",
|
||||||
|
primaryKeys: ["id"],
|
||||||
|
linkableKeys: { cart_id: "Cart" },
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: "cart",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relationships: [
|
||||||
|
{
|
||||||
|
serviceName: Modules.PRODUCT,
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "variant_id",
|
||||||
|
alias: "variant",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "Variants",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: "regionService",
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "region_id",
|
||||||
|
alias: "region",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: "customerService",
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "customer_id",
|
||||||
|
alias: "customer",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as ModuleJoinerConfig
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
serviceName: "customerService",
|
||||||
|
primaryKeys: ["id"],
|
||||||
|
linkableKeys: { customer_id: "Customer" },
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: "customer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "customers",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as ModuleJoinerConfig
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * as cart from "./cart-service"
|
||||||
|
export * as customer from "./customer-service"
|
||||||
|
export * as region from "./region-service"
|
||||||
|
export * as shippingProfile from "./shipping-profile-service"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
serviceName: "regionService",
|
||||||
|
primaryKeys: ["id"],
|
||||||
|
linkableKeys: { region_id: "Region" },
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: "region",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "regions",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as ModuleJoinerConfig
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
serviceName: "shippingProfileService",
|
||||||
|
primaryKeys: ["id"],
|
||||||
|
linkableKeys: { profile_id: "ShippingProfile" },
|
||||||
|
schema: `
|
||||||
|
scalar Date
|
||||||
|
scalar JSON
|
||||||
|
|
||||||
|
type ShippingProfile {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
type: String!
|
||||||
|
created_at: Date!
|
||||||
|
updated_at: Date!
|
||||||
|
deleted_at: Date
|
||||||
|
metadata: JSON
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: "shipping_profile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "shipping_profiles",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as ModuleJoinerConfig
|
||||||
@@ -16,11 +16,11 @@ export const InventoryModule = {
|
|||||||
__joinerConfig: {
|
__joinerConfig: {
|
||||||
serviceName: "inventoryService",
|
serviceName: "inventoryService",
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
linkableKeys: [
|
linkableKeys: {
|
||||||
"inventory_item_id",
|
inventory_item_id: "InventoryItem",
|
||||||
"inventory_level_id",
|
inventory_level_id: "InventoryLevel",
|
||||||
"reservation_item_id",
|
reservation_item_id: "ReservationItem",
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
softDelete: jest.fn(() => {}),
|
softDelete: jest.fn(() => {}),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const ProductModule = {
|
|||||||
__joinerConfig: {
|
__joinerConfig: {
|
||||||
serviceName: "productService",
|
serviceName: "productService",
|
||||||
primaryKeys: ["id", "handle"],
|
primaryKeys: ["id", "handle"],
|
||||||
linkableKeys: ["product_id", "variant_id"],
|
linkableKeys: { product_id: "Product", variant_id: "ProductVariant" },
|
||||||
alias: [],
|
alias: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const StockLocationModule = {
|
|||||||
__joinerConfig: {
|
__joinerConfig: {
|
||||||
serviceName: "stockLocationService",
|
serviceName: "stockLocationService",
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
linkableKeys: ["stock_location_id"],
|
linkableKeys: { stock_location_id: "StockLocation" },
|
||||||
alias: [],
|
alias: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { cleanGraphQLSchema } from "../utils/clean-graphql-schema"
|
||||||
|
|
||||||
|
describe("Clean Graphql Schema", function () {
|
||||||
|
it("Should keep the schema intact if all entities are available", function () {
|
||||||
|
const schemaStr = `
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
variants: [Variant]!
|
||||||
|
}
|
||||||
|
type Variant {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
product_id: ID!
|
||||||
|
product: Product!
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const { schema, notFound } = cleanGraphQLSchema(schemaStr)
|
||||||
|
|
||||||
|
expect(schema.replace(/\s/g, "")).toEqual(schemaStr.replace(/\s/g, ""))
|
||||||
|
expect(notFound).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should remove fields where the relation doesn't exist", function () {
|
||||||
|
const schemaStr = `
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
variants: [Variant!]!
|
||||||
|
profile: ShippingProfile!
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const expectedStr = `
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const { schema, notFound } = cleanGraphQLSchema(schemaStr)
|
||||||
|
|
||||||
|
expect(schema.replace(/\s/g, "")).toEqual(expectedStr.replace(/\s/g, ""))
|
||||||
|
expect(notFound).toEqual({
|
||||||
|
Product: { variants: "Variant", profile: "ShippingProfile" },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should remove fields where the relation doesn't exist and flag extended entity where the main entity doesn't exist", function () {
|
||||||
|
const schemaStr = `
|
||||||
|
scalar JSON
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
variants: [Variant!]!
|
||||||
|
profile: ShippingProfile!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Variant {
|
||||||
|
metadata: JSON
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const expectedStr = `
|
||||||
|
scalar JSON
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const { schema, notFound } = cleanGraphQLSchema(schemaStr)
|
||||||
|
|
||||||
|
expect(schema.replace(/\s/g, "")).toEqual(expectedStr.replace(/\s/g, ""))
|
||||||
|
expect(notFound).toEqual({
|
||||||
|
Product: { variants: "Variant", profile: "ShippingProfile" },
|
||||||
|
Variant: { __extended: "" },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should remove fields from extend where the relation doesn't exist", function () {
|
||||||
|
const schemaStr = `
|
||||||
|
scalar JSON
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
variants: [Variant!]!
|
||||||
|
profile: ShippingProfile!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Product {
|
||||||
|
variants: [Variant!]!
|
||||||
|
profile: ShippingProfile!
|
||||||
|
metadata: JSON
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const expectedStr = `
|
||||||
|
scalar JSON
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Product {
|
||||||
|
metadata: JSON
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const { schema, notFound } = cleanGraphQLSchema(schemaStr)
|
||||||
|
|
||||||
|
expect(schema.replace(/\s/g, "")).toEqual(expectedStr.replace(/\s/g, ""))
|
||||||
|
expect(notFound).toEqual({
|
||||||
|
Product: { variants: "Variant", profile: "ShippingProfile" },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { mergeTypeDefs } from "@graphql-tools/merge"
|
||||||
|
import { makeExecutableSchema } from "@graphql-tools/schema"
|
||||||
import { RemoteFetchDataCallback } from "@medusajs/orchestration"
|
import { RemoteFetchDataCallback } from "@medusajs/orchestration"
|
||||||
import {
|
import {
|
||||||
ExternalModuleDeclaration,
|
ExternalModuleDeclaration,
|
||||||
@@ -12,13 +14,14 @@ import {
|
|||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
isObject,
|
|
||||||
ModulesSdkUtils,
|
ModulesSdkUtils,
|
||||||
|
isObject,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { MODULE_PACKAGE_NAMES, Modules } from "./definitions"
|
import { MODULE_PACKAGE_NAMES, Modules } from "./definitions"
|
||||||
import { MedusaModule } from "./medusa-module"
|
import { MedusaModule } from "./medusa-module"
|
||||||
import { RemoteLink } from "./remote-link"
|
import { RemoteLink } from "./remote-link"
|
||||||
import { RemoteQuery } from "./remote-query"
|
import { RemoteQuery } from "./remote-query"
|
||||||
|
import { cleanGraphQLSchema } from "./utils"
|
||||||
|
|
||||||
export type MedusaModuleConfig = {
|
export type MedusaModuleConfig = {
|
||||||
[key: string | Modules]:
|
[key: string | Modules]:
|
||||||
@@ -46,72 +49,19 @@ export type SharedResources = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function MedusaApp({
|
async function loadModules(modulesConfig, injectedDependencies) {
|
||||||
sharedResourcesConfig,
|
const allModules = {}
|
||||||
servicesConfig,
|
|
||||||
modulesConfigPath,
|
|
||||||
modulesConfigFileName,
|
|
||||||
modulesConfig,
|
|
||||||
linkModules,
|
|
||||||
remoteFetchData,
|
|
||||||
injectedDependencies = {},
|
|
||||||
}: {
|
|
||||||
sharedResourcesConfig?: SharedResources
|
|
||||||
loadedModules?: LoadedModule[]
|
|
||||||
servicesConfig?: ModuleJoinerConfig[]
|
|
||||||
modulesConfigPath?: string
|
|
||||||
modulesConfigFileName?: string
|
|
||||||
modulesConfig?: MedusaModuleConfig
|
|
||||||
linkModules?: ModuleJoinerConfig | ModuleJoinerConfig[]
|
|
||||||
remoteFetchData?: RemoteFetchDataCallback
|
|
||||||
injectedDependencies?: any
|
|
||||||
}): Promise<{
|
|
||||||
modules: Record<string, LoadedModule | LoadedModule[]>
|
|
||||||
link: RemoteLink | undefined
|
|
||||||
query: (
|
|
||||||
query: string | RemoteJoinerQuery | object,
|
|
||||||
variables?: Record<string, unknown>
|
|
||||||
) => Promise<any>
|
|
||||||
}> {
|
|
||||||
const modules: MedusaModuleConfig =
|
|
||||||
modulesConfig ??
|
|
||||||
(
|
|
||||||
await import(
|
|
||||||
modulesConfigPath ??
|
|
||||||
process.cwd() + (modulesConfigFileName ?? "/modules-config")
|
|
||||||
)
|
|
||||||
).default
|
|
||||||
|
|
||||||
const dbData = ModulesSdkUtils.loadDatabaseConfig(
|
|
||||||
"medusa",
|
|
||||||
sharedResourcesConfig as ModuleServiceInitializeOptions,
|
|
||||||
true
|
|
||||||
)!
|
|
||||||
|
|
||||||
if (
|
|
||||||
dbData.clientUrl &&
|
|
||||||
!injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION]
|
|
||||||
) {
|
|
||||||
injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION] =
|
|
||||||
ModulesSdkUtils.createPgConnection({
|
|
||||||
...(sharedResourcesConfig?.database ?? {}),
|
|
||||||
...dbData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const allModules: Record<string, LoadedModule | LoadedModule[]> = {}
|
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.keys(modules).map(async (moduleName) => {
|
Object.keys(modulesConfig).map(async (moduleName) => {
|
||||||
const mod = modules[moduleName] as MedusaModuleConfig
|
const mod = modulesConfig[moduleName]
|
||||||
|
|
||||||
let path: string
|
let path: string
|
||||||
let declaration: any = {}
|
let declaration: any = {}
|
||||||
|
let definition: ModuleDefinition | undefined = undefined
|
||||||
|
|
||||||
if (isObject(mod)) {
|
if (isObject(mod)) {
|
||||||
const mod_ = mod as unknown as InternalModuleDeclaration
|
const mod_ = mod as unknown as InternalModuleDeclaration
|
||||||
path = mod_.resolve ?? MODULE_PACKAGE_NAMES[moduleName]
|
path = mod_.resolve ?? MODULE_PACKAGE_NAMES[moduleName]
|
||||||
|
definition = mod_.definition
|
||||||
declaration = { ...mod }
|
declaration = { ...mod }
|
||||||
delete declaration.definition
|
delete declaration.definition
|
||||||
} else {
|
} else {
|
||||||
@@ -119,7 +69,6 @@ export async function MedusaApp({
|
|||||||
}
|
}
|
||||||
|
|
||||||
declaration.scope ??= MODULE_SCOPE.INTERNAL
|
declaration.scope ??= MODULE_SCOPE.INTERNAL
|
||||||
|
|
||||||
if (
|
if (
|
||||||
declaration.scope === MODULE_SCOPE.INTERNAL &&
|
declaration.scope === MODULE_SCOPE.INTERNAL &&
|
||||||
!declaration.resources
|
!declaration.resources
|
||||||
@@ -133,7 +82,7 @@ export async function MedusaApp({
|
|||||||
declaration,
|
declaration,
|
||||||
undefined,
|
undefined,
|
||||||
injectedDependencies,
|
injectedDependencies,
|
||||||
(isObject(mod) ? mod.definition : undefined) as ModuleDefinition
|
definition
|
||||||
)) as LoadedModule
|
)) as LoadedModule
|
||||||
|
|
||||||
if (allModules[moduleName] && !Array.isArray(allModules[moduleName])) {
|
if (allModules[moduleName] && !Array.isArray(allModules[moduleName])) {
|
||||||
@@ -145,33 +94,117 @@ export async function MedusaApp({
|
|||||||
} else {
|
} else {
|
||||||
allModules[moduleName] = loaded[moduleName]
|
allModules[moduleName] = loaded[moduleName]
|
||||||
}
|
}
|
||||||
|
|
||||||
return loaded
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
return allModules
|
||||||
|
}
|
||||||
|
|
||||||
let link: RemoteLink | undefined = undefined
|
async function initializeLinks(linkModules, injectedDependencies) {
|
||||||
let query: (
|
|
||||||
query: string | RemoteJoinerQuery | object,
|
|
||||||
variables?: Record<string, unknown>
|
|
||||||
) => Promise<any>
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { initialize: initializeLinks } = await import(
|
const { initialize: initializeLinks } = await import(
|
||||||
"@medusajs/link-modules" as string
|
"@medusajs/link-modules" as string
|
||||||
)
|
)
|
||||||
await initializeLinks({}, linkModules, injectedDependencies)
|
await initializeLinks({}, linkModules, injectedDependencies)
|
||||||
|
return new RemoteLink()
|
||||||
link = new RemoteLink()
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("Error initializing link modules.", err)
|
console.warn("Error initializing link modules.", err)
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanAndMergeSchema(loadedSchema) {
|
||||||
|
const { schema: cleanedSchema, notFound } = cleanGraphQLSchema(loadedSchema)
|
||||||
|
const mergedSchema = mergeTypeDefs(cleanedSchema)
|
||||||
|
return { schema: makeExecutableSchema({ typeDefs: mergedSchema }), notFound }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoadedSchema(): string {
|
||||||
|
return MedusaModule.getAllJoinerConfigs()
|
||||||
|
.map((joinerConfig) => joinerConfig?.schema ?? "")
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerCustomJoinerConfigs(servicesConfig: ModuleJoinerConfig[]) {
|
||||||
|
for (const config of servicesConfig) {
|
||||||
|
if (!config.serviceName || config.isReadOnlyLink) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
MedusaModule.setJoinerConfig(config.serviceName, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function MedusaApp(
|
||||||
|
{
|
||||||
|
sharedResourcesConfig,
|
||||||
|
servicesConfig,
|
||||||
|
modulesConfigPath,
|
||||||
|
modulesConfigFileName,
|
||||||
|
modulesConfig,
|
||||||
|
linkModules,
|
||||||
|
remoteFetchData,
|
||||||
|
injectedDependencies,
|
||||||
|
}: {
|
||||||
|
sharedResourcesConfig?: SharedResources
|
||||||
|
loadedModules?: LoadedModule[]
|
||||||
|
servicesConfig?: ModuleJoinerConfig[]
|
||||||
|
modulesConfigPath?: string
|
||||||
|
modulesConfigFileName?: string
|
||||||
|
modulesConfig?: MedusaModuleConfig
|
||||||
|
linkModules?: ModuleJoinerConfig | ModuleJoinerConfig[]
|
||||||
|
remoteFetchData?: RemoteFetchDataCallback
|
||||||
|
injectedDependencies?: any
|
||||||
|
} = {
|
||||||
|
injectedDependencies: {},
|
||||||
|
}
|
||||||
|
): Promise<{
|
||||||
|
modules: Record<string, LoadedModule | LoadedModule[]>
|
||||||
|
link: RemoteLink | undefined
|
||||||
|
query: (
|
||||||
|
query: string | RemoteJoinerQuery | object,
|
||||||
|
variables?: Record<string, unknown>
|
||||||
|
) => Promise<any>
|
||||||
|
entitiesMap?: Record<string, any>
|
||||||
|
notFound?: Record<string, Record<string, string>>
|
||||||
|
}> {
|
||||||
|
const modules: MedusaModuleConfig =
|
||||||
|
modulesConfig ??
|
||||||
|
(
|
||||||
|
await import(
|
||||||
|
modulesConfigPath ??
|
||||||
|
process.cwd() + (modulesConfigFileName ?? "/modules-config")
|
||||||
|
)
|
||||||
|
).default
|
||||||
|
const dbData = ModulesSdkUtils.loadDatabaseConfig(
|
||||||
|
"medusa",
|
||||||
|
sharedResourcesConfig as ModuleServiceInitializeOptions,
|
||||||
|
true
|
||||||
|
)!
|
||||||
|
|
||||||
|
registerCustomJoinerConfigs(servicesConfig ?? [])
|
||||||
|
|
||||||
|
if (
|
||||||
|
dbData.clientUrl &&
|
||||||
|
!injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION]
|
||||||
|
) {
|
||||||
|
injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION] =
|
||||||
|
ModulesSdkUtils.createPgConnection({
|
||||||
|
...(sharedResourcesConfig?.database ?? {}),
|
||||||
|
...dbData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const allModules = await loadModules(modules, injectedDependencies)
|
||||||
|
const link = await initializeLinks(linkModules, injectedDependencies)
|
||||||
|
|
||||||
|
const loadedSchema = getLoadedSchema()
|
||||||
|
const { schema, notFound } = cleanAndMergeSchema(loadedSchema)
|
||||||
|
|
||||||
const remoteQuery = new RemoteQuery({
|
const remoteQuery = new RemoteQuery({
|
||||||
servicesConfig,
|
servicesConfig,
|
||||||
customRemoteFetchData: remoteFetchData,
|
customRemoteFetchData: remoteFetchData,
|
||||||
})
|
})
|
||||||
query = async (
|
const query = async (
|
||||||
query: string | RemoteJoinerQuery | object,
|
query: string | RemoteJoinerQuery | object,
|
||||||
variables?: Record<string, unknown>
|
variables?: Record<string, unknown>
|
||||||
) => {
|
) => {
|
||||||
@@ -182,5 +215,7 @@ export async function MedusaApp({
|
|||||||
modules: allModules,
|
modules: allModules,
|
||||||
link,
|
link,
|
||||||
query,
|
query,
|
||||||
|
entitiesMap: schema.getTypeMap(),
|
||||||
|
notFound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +52,7 @@ export class MedusaModule {
|
|||||||
private static instances_: Map<string, any> = new Map()
|
private static instances_: Map<string, any> = new Map()
|
||||||
private static modules_: Map<string, ModuleAlias[]> = new Map()
|
private static modules_: Map<string, ModuleAlias[]> = new Map()
|
||||||
private static loading_: Map<string, Promise<any>> = new Map()
|
private static loading_: Map<string, Promise<any>> = new Map()
|
||||||
|
private static joinerConfig_: Map<string, ModuleJoinerConfig> = new Map()
|
||||||
|
|
||||||
public static getLoadedModules(
|
public static getLoadedModules(
|
||||||
aliases?: Map<string, string>
|
aliases?: Map<string, string>
|
||||||
@@ -68,6 +69,7 @@ export class MedusaModule {
|
|||||||
public static clearInstances(): void {
|
public static clearInstances(): void {
|
||||||
MedusaModule.instances_.clear()
|
MedusaModule.instances_.clear()
|
||||||
MedusaModule.modules_.clear()
|
MedusaModule.modules_.clear()
|
||||||
|
MedusaModule.joinerConfig_.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isInstalled(moduleKey: string, alias?: string): boolean {
|
public static isInstalled(moduleKey: string, alias?: string): boolean {
|
||||||
@@ -81,6 +83,22 @@ export class MedusaModule {
|
|||||||
return MedusaModule.modules_.has(moduleKey)
|
return MedusaModule.modules_.has(moduleKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static getJoinerConfig(moduleKey: string): ModuleJoinerConfig {
|
||||||
|
return MedusaModule.joinerConfig_.get(moduleKey)!
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getAllJoinerConfigs(): ModuleJoinerConfig[] {
|
||||||
|
return [...MedusaModule.joinerConfig_.values()]
|
||||||
|
}
|
||||||
|
|
||||||
|
public static setJoinerConfig(
|
||||||
|
moduleKey: string,
|
||||||
|
config: ModuleJoinerConfig
|
||||||
|
): ModuleJoinerConfig {
|
||||||
|
MedusaModule.joinerConfig_.set(moduleKey, config)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
public static getModuleInstance(
|
public static getModuleInstance(
|
||||||
moduleKey: string,
|
moduleKey: string,
|
||||||
alias?: string
|
alias?: string
|
||||||
@@ -218,6 +236,7 @@ export class MedusaModule {
|
|||||||
].__joinerConfig()
|
].__joinerConfig()
|
||||||
|
|
||||||
services[keyName].__joinerConfig = joinerConfig
|
services[keyName].__joinerConfig = joinerConfig
|
||||||
|
MedusaModule.setJoinerConfig(keyName, joinerConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
MedusaModule.registerModule(keyName, {
|
MedusaModule.registerModule(keyName, {
|
||||||
@@ -329,6 +348,7 @@ export class MedusaModule {
|
|||||||
].__joinerConfig()
|
].__joinerConfig()
|
||||||
|
|
||||||
services[keyName].__joinerConfig = joinerConfig
|
services[keyName].__joinerConfig = joinerConfig
|
||||||
|
MedusaModule.setJoinerConfig(keyName, joinerConfig)
|
||||||
|
|
||||||
if (!joinerConfig.isLink) {
|
if (!joinerConfig.isLink) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -147,7 +147,10 @@ export class RemoteLink {
|
|||||||
|
|
||||||
private getLinkableKeys(mod: LoadedLinkModule) {
|
private getLinkableKeys(mod: LoadedLinkModule) {
|
||||||
return (
|
return (
|
||||||
mod.__joinerConfig.linkableKeys ?? mod.__joinerConfig.primaryKeys ?? []
|
(mod.__joinerConfig.linkableKeys &&
|
||||||
|
Object.keys(mod.__joinerConfig.linkableKeys)) ||
|
||||||
|
mod.__joinerConfig.primaryKeys ||
|
||||||
|
[]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { Kind, parse, print, visit } from "graphql"
|
||||||
|
|
||||||
|
export function cleanGraphQLSchema(schema: string): {
|
||||||
|
schema: string
|
||||||
|
notFound: Record<string, Record<string, string>>
|
||||||
|
} {
|
||||||
|
const extractTypeNameAndKind = (type) => {
|
||||||
|
if (type.kind === Kind.NAMED_TYPE) {
|
||||||
|
return [type.name.value, type.kind]
|
||||||
|
}
|
||||||
|
if (type.kind === Kind.NON_NULL_TYPE || type.kind === Kind.LIST_TYPE) {
|
||||||
|
return extractTypeNameAndKind(type.type)
|
||||||
|
}
|
||||||
|
return [null, null]
|
||||||
|
}
|
||||||
|
|
||||||
|
const ast = parse(schema)
|
||||||
|
|
||||||
|
const typeNames = new Set(["String", "Int", "Float", "Boolean", "ID"])
|
||||||
|
const extendedTypes = new Set()
|
||||||
|
|
||||||
|
const kinds = [
|
||||||
|
Kind.OBJECT_TYPE_DEFINITION,
|
||||||
|
Kind.INTERFACE_TYPE_DEFINITION,
|
||||||
|
Kind.ENUM_TYPE_DEFINITION,
|
||||||
|
Kind.SCALAR_TYPE_DEFINITION,
|
||||||
|
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
||||||
|
Kind.UNION_TYPE_DEFINITION,
|
||||||
|
]
|
||||||
|
ast.definitions.forEach((def: any) => {
|
||||||
|
if (kinds.includes(def.kind)) {
|
||||||
|
typeNames.add(def.name.value)
|
||||||
|
} else if (def.kind === Kind.OBJECT_TYPE_EXTENSION) {
|
||||||
|
extendedTypes.add(def.name.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const nonExistingMap: Record<string, Record<string, string>> = {}
|
||||||
|
const parentStack: string[] = []
|
||||||
|
|
||||||
|
/*
|
||||||
|
Traverse the graph mapping all the entities + fields and removing the ones that don't exist.
|
||||||
|
Extensions are not removed, but marked with a "__extended" key if the main entity doesn't exist. (example: Link modules injecting fields into another module)
|
||||||
|
*/
|
||||||
|
const cleanedAst = visit(ast, {
|
||||||
|
ObjectTypeExtension: {
|
||||||
|
enter(node) {
|
||||||
|
const typeName = node.name.value
|
||||||
|
|
||||||
|
parentStack.push(typeName)
|
||||||
|
if (!typeNames.has(typeName)) {
|
||||||
|
nonExistingMap[typeName] ??= {}
|
||||||
|
nonExistingMap[typeName]["__extended"] = ""
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
leave() {
|
||||||
|
parentStack.pop()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ObjectTypeDefinition: {
|
||||||
|
enter(node) {
|
||||||
|
parentStack.push(node.name.value)
|
||||||
|
},
|
||||||
|
leave() {
|
||||||
|
parentStack.pop()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FieldDefinition: {
|
||||||
|
leave(node) {
|
||||||
|
const [typeName, kind] = extractTypeNameAndKind(node.type)
|
||||||
|
|
||||||
|
if (!typeNames.has(typeName) && kind === Kind.NAMED_TYPE) {
|
||||||
|
const currentParent = parentStack[parentStack.length - 1]
|
||||||
|
|
||||||
|
nonExistingMap[currentParent] ??= {}
|
||||||
|
nonExistingMap[currentParent][node.name.value] = typeName
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return the schema and the map of non existing entities and fields
|
||||||
|
return {
|
||||||
|
schema: print(cleanedAst),
|
||||||
|
notFound: nonExistingMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
|
export * from "./clean-graphql-schema"
|
||||||
export * from "./get-fields-and-relations"
|
export * from "./get-fields-and-relations"
|
||||||
export * from "./graphql-schema-to-fields"
|
export * from "./graphql-schema-to-fields"
|
||||||
|
|
||||||
@@ -1,30 +1,27 @@
|
|||||||
import { Modules } from "@medusajs/modules-sdk"
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
import { MapToConfig } from "@medusajs/utils"
|
import { MapToConfig } from "@medusajs/utils"
|
||||||
import * as Models from "@models"
|
import { Currency, MoneyAmount, PriceSet } from "@models"
|
||||||
|
|
||||||
export enum LinkableKeys {
|
export const LinkableKeys = {
|
||||||
MONEY_AMOUNT_ID = "money_amount_id",
|
money_amount_id: MoneyAmount.name,
|
||||||
CURRENCY_CODE = "currency_code",
|
currency_code: Currency.name,
|
||||||
PRICE_SET_ID = "price_set_id",
|
price_set_id: PriceSet.name,
|
||||||
}
|
|
||||||
|
|
||||||
export const entityNameToLinkableKeysMap: MapToConfig = {
|
|
||||||
[Models.PriceSet.name]: [
|
|
||||||
{ mapTo: LinkableKeys.PRICE_SET_ID, valueFrom: "id" },
|
|
||||||
],
|
|
||||||
[Models.Currency.name]: [
|
|
||||||
{ mapTo: LinkableKeys.CURRENCY_CODE, valueFrom: "code" },
|
|
||||||
],
|
|
||||||
[Models.MoneyAmount.name]: [
|
|
||||||
{ mapTo: LinkableKeys.MONEY_AMOUNT_ID, valueFrom: "id" },
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
const entityLinkableKeysMap: MapToConfig = {}
|
||||||
|
Object.entries(LinkableKeys).forEach(([key, value]) => {
|
||||||
|
entityLinkableKeysMap[value] ??= []
|
||||||
|
entityLinkableKeysMap[value].push({
|
||||||
|
mapTo: key,
|
||||||
|
valueFrom: key.split("_").pop()!,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
|
||||||
|
|
||||||
export const joinerConfig: ModuleJoinerConfig = {
|
export const joinerConfig: ModuleJoinerConfig = {
|
||||||
serviceName: Modules.PRICING,
|
serviceName: Modules.PRICING,
|
||||||
primaryKeys: ["id", "currency_code"],
|
primaryKeys: ["id", "currency_code"],
|
||||||
linkableKeys: Object.values(LinkableKeys),
|
linkableKeys: LinkableKeys,
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
name: "price_set",
|
name: "price_set",
|
||||||
|
|||||||
@@ -11,54 +11,36 @@ import {
|
|||||||
ProductVariant,
|
ProductVariant,
|
||||||
} from "@models"
|
} from "@models"
|
||||||
import ProductImage from "./models/product-image"
|
import ProductImage from "./models/product-image"
|
||||||
|
import moduleSchema from "./schema"
|
||||||
|
|
||||||
export enum LinkableKeys {
|
export const LinkableKeys = {
|
||||||
PRODUCT_ID = "product_id", // Main service ID must the first
|
product_id: Product.name,
|
||||||
PRODUCT_HANDLE = "product_handle",
|
product_handle: Product.name,
|
||||||
VARIANT_ID = "variant_id",
|
variant_id: ProductVariant.name,
|
||||||
VARIANT_SKU = "variant_sku",
|
variant_sku: ProductVariant.name,
|
||||||
PRODUCT_OPTION_ID = "product_option_id",
|
product_option_id: ProductOption.name,
|
||||||
PRODUCT_TYPE_ID = "product_type_id",
|
product_type_id: ProductType.name,
|
||||||
PRODUCT_CATEGORY_ID = "product_category_id",
|
product_category_id: ProductCategory.name,
|
||||||
PRODUCT_COLLECTION_ID = "product_collection_id",
|
product_collection_id: ProductCollection.name,
|
||||||
PRODUCT_TAG_ID = "product_tag_id",
|
product_tag_id: ProductTag.name,
|
||||||
PRODUCT_IMAGE_ID = "product_image_id",
|
product_image_id: ProductImage.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const entityNameToLinkableKeysMap: MapToConfig = {
|
const entityLinkableKeysMap: MapToConfig = {}
|
||||||
[Product.name]: [
|
Object.entries(LinkableKeys).forEach(([key, value]) => {
|
||||||
{ mapTo: LinkableKeys.PRODUCT_ID, valueFrom: "id" },
|
entityLinkableKeysMap[value] ??= []
|
||||||
{
|
entityLinkableKeysMap[value].push({
|
||||||
mapTo: LinkableKeys.PRODUCT_HANDLE,
|
mapTo: key,
|
||||||
valueFrom: "handle",
|
valueFrom: key.split("_").pop()!,
|
||||||
},
|
})
|
||||||
],
|
})
|
||||||
[ProductVariant.name]: [
|
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
|
||||||
{ mapTo: LinkableKeys.VARIANT_ID, valueFrom: "id" },
|
|
||||||
{ mapTo: LinkableKeys.VARIANT_SKU, valueFrom: "sku" },
|
|
||||||
],
|
|
||||||
[ProductOption.name]: [
|
|
||||||
{ mapTo: LinkableKeys.PRODUCT_OPTION_ID, valueFrom: "id" },
|
|
||||||
],
|
|
||||||
[ProductType.name]: [
|
|
||||||
{ mapTo: LinkableKeys.PRODUCT_TYPE_ID, valueFrom: "id" },
|
|
||||||
],
|
|
||||||
[ProductCategory.name]: [
|
|
||||||
{ mapTo: LinkableKeys.PRODUCT_CATEGORY_ID, valueFrom: "id" },
|
|
||||||
],
|
|
||||||
[ProductCollection.name]: [
|
|
||||||
{ mapTo: LinkableKeys.PRODUCT_COLLECTION_ID, valueFrom: "id" },
|
|
||||||
],
|
|
||||||
[ProductTag.name]: [{ mapTo: LinkableKeys.PRODUCT_TAG_ID, valueFrom: "id" }],
|
|
||||||
[ProductImage.name]: [
|
|
||||||
{ mapTo: LinkableKeys.PRODUCT_IMAGE_ID, valueFrom: "id" },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export const joinerConfig: ModuleJoinerConfig = {
|
export const joinerConfig: ModuleJoinerConfig = {
|
||||||
serviceName: Modules.PRODUCT,
|
serviceName: Modules.PRODUCT,
|
||||||
primaryKeys: ["id", "handle"],
|
primaryKeys: ["id", "handle"],
|
||||||
linkableKeys: Object.values(LinkableKeys),
|
linkableKeys: LinkableKeys,
|
||||||
|
schema: moduleSchema,
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
name: "product",
|
name: "product",
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
export default `
|
||||||
|
scalar Date
|
||||||
|
scalar JSON
|
||||||
|
|
||||||
|
enum ProductStatus {
|
||||||
|
DRAFT
|
||||||
|
PUBLISHED
|
||||||
|
ARCHIVED
|
||||||
|
}
|
||||||
|
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
handle: String
|
||||||
|
subtitle: String
|
||||||
|
description: String
|
||||||
|
isGiftcard: Boolean!
|
||||||
|
status: ProductStatus!
|
||||||
|
thumbnail: String
|
||||||
|
options: [ProductOption]
|
||||||
|
variants: [ProductVariant]
|
||||||
|
weight: Float
|
||||||
|
length: Float
|
||||||
|
height: Float
|
||||||
|
width: Float
|
||||||
|
originCountry: String
|
||||||
|
hsCode: String
|
||||||
|
midCode: String
|
||||||
|
material: String
|
||||||
|
collectionId: String
|
||||||
|
collection: ProductCollection
|
||||||
|
typeId: String
|
||||||
|
type: ProductType!
|
||||||
|
tags: [ProductTag]
|
||||||
|
images: [ProductImage]
|
||||||
|
categories: [ProductCategory]
|
||||||
|
discountable: Boolean!
|
||||||
|
externalId: String
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
deletedAt: Date
|
||||||
|
metadata: JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductVariant {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
sku: String
|
||||||
|
barcode: String
|
||||||
|
ean: String
|
||||||
|
upc: String
|
||||||
|
inventoryQuantity: Float!
|
||||||
|
allowBackorder: Boolean!
|
||||||
|
manageInventory: Boolean!
|
||||||
|
hsCode: String
|
||||||
|
originCountry: String
|
||||||
|
midCode: String
|
||||||
|
material: String
|
||||||
|
weight: Float
|
||||||
|
length: Float
|
||||||
|
height: Float
|
||||||
|
width: Float
|
||||||
|
metadata: JSON
|
||||||
|
variantRank: Float
|
||||||
|
productId: String!
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
deletedAt: Date
|
||||||
|
product: Product!
|
||||||
|
options: [ProductOptionValue]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductType {
|
||||||
|
id: ID!
|
||||||
|
value: String!
|
||||||
|
metadata: JSON
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
deletedAt: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductTag {
|
||||||
|
id: ID!
|
||||||
|
value: String!
|
||||||
|
metadata: JSON
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
deletedAt: Date
|
||||||
|
products: [Product]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductOption {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
productId: String!
|
||||||
|
product: Product!
|
||||||
|
values: [ProductOptionValue]
|
||||||
|
metadata: JSON
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
deletedAt: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductOptionValue {
|
||||||
|
id: ID!
|
||||||
|
value: String!
|
||||||
|
optionId: String!
|
||||||
|
option: ProductOption!
|
||||||
|
variantId: String!
|
||||||
|
variant: ProductVariant!
|
||||||
|
metadata: JSON
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
deletedAt: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductImage {
|
||||||
|
id: ID!
|
||||||
|
url: String!
|
||||||
|
metadata: JSON
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
deletedAt: Date
|
||||||
|
products: [Product]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductCollection {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
handle: String!
|
||||||
|
products: [Product]
|
||||||
|
metadata: JSON
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
deletedAt: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductCategory {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
description: String!
|
||||||
|
handle: String!
|
||||||
|
mpath: String!
|
||||||
|
isActive: Boolean!
|
||||||
|
isInternal: Boolean!
|
||||||
|
rank: Float!
|
||||||
|
parentCategoryId: String
|
||||||
|
parentCategory: ProductCategory
|
||||||
|
categoryChildren: [ProductCategory]
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
products: [Product]
|
||||||
|
}
|
||||||
|
`
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
import { Modules } from "@medusajs/modules-sdk"
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
|
import { StockLocation } from "./models"
|
||||||
|
|
||||||
export const joinerConfig: ModuleJoinerConfig = {
|
export const joinerConfig: ModuleJoinerConfig = {
|
||||||
serviceName: Modules.STOCK_LOCATION,
|
serviceName: Modules.STOCK_LOCATION,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
linkableKeys: ["stock_location_id"],
|
linkableKeys: { stock_location_id: StockLocation.name },
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
name: "stock_location",
|
name: "stock_location",
|
||||||
|
|||||||
@@ -133,6 +133,10 @@ export type ModuleJoinerConfig = Omit<
|
|||||||
JoinerServiceConfig,
|
JoinerServiceConfig,
|
||||||
"serviceName" | "primaryKeys" | "relationships" | "extends"
|
"serviceName" | "primaryKeys" | "relationships" | "extends"
|
||||||
> & {
|
> & {
|
||||||
|
/**
|
||||||
|
* GraphQL schema for the all module's available entities and fields
|
||||||
|
*/
|
||||||
|
schema?: string
|
||||||
relationships?: ModuleJoinerRelationship[]
|
relationships?: ModuleJoinerRelationship[]
|
||||||
extends?: {
|
extends?: {
|
||||||
serviceName: string
|
serviceName: string
|
||||||
@@ -153,9 +157,9 @@ export type ModuleJoinerConfig = Omit<
|
|||||||
*/
|
*/
|
||||||
isLink?: boolean
|
isLink?: boolean
|
||||||
/**
|
/**
|
||||||
* Keys that can be used to link to other modules
|
* Keys that can be used to link to other modules. e.g { product_id: "Product" } "Product" being the entity it refers to
|
||||||
*/
|
*/
|
||||||
linkableKeys?: string[]
|
linkableKeys?: Record<string, string>
|
||||||
/**
|
/**
|
||||||
* If true it expands a RemoteQuery property but doesn't create a pivot table
|
* If true it expands a RemoteQuery property but doesn't create a pivot table
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export const camelToSnakeCase = (string) =>
|
||||||
|
string.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
||||||
@@ -22,6 +22,7 @@ export * from "./set-metadata"
|
|||||||
export * from "./simple-hash"
|
export * from "./simple-hash"
|
||||||
export * from "./string-to-select-relation-object"
|
export * from "./string-to-select-relation-object"
|
||||||
export * from "./stringify-circular"
|
export * from "./stringify-circular"
|
||||||
|
export * from "./camel-to-snake-case"
|
||||||
export * from "./to-camel-case"
|
export * from "./to-camel-case"
|
||||||
export * from "./to-kebab-case"
|
export * from "./to-kebab-case"
|
||||||
export * from "./to-pascal-case"
|
export * from "./to-pascal-case"
|
||||||
|
|||||||
Reference in new issue
Block a user