chore(): Reorganize modules (#7210)

**What**
Move all modules to the modules directory
This commit is contained in:
Adrien de Peretti
2024-05-02 17:33:34 +02:00
committed by GitHub
parent 7a351eef09
commit 4eae25e1ef
870 changed files with 91 additions and 62 deletions

View File

@@ -0,0 +1,28 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
export const CartCustomer: ModuleJoinerConfig = {
isLink: true,
isReadOnlyLink: true,
extends: [
{
serviceName: Modules.CART,
relationship: {
serviceName: Modules.CUSTOMER,
primaryKey: "id",
foreignKey: "customer_id",
alias: "customer",
},
},
{
serviceName: Modules.CUSTOMER,
relationship: {
serviceName: Modules.CART,
primaryKey: "customer_id",
foreignKey: "id",
alias: "carts",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,61 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const CartPaymentCollection: ModuleJoinerConfig = {
serviceName: LINKS.CartPaymentCollection,
isLink: true,
databaseConfig: {
tableName: "cart_payment_collection",
idPrefix: "capaycol",
},
alias: [
{
name: ["cart_payment_collection", "cart_payment_collections"],
args: {
entity: "LinkCartPaymentCollection",
},
},
],
primaryKeys: ["id", "cart_id", "payment_collection_id"],
relationships: [
{
serviceName: Modules.CART,
primaryKey: "id",
foreignKey: "cart_id",
alias: "cart",
},
{
serviceName: Modules.PAYMENT,
primaryKey: "id",
foreignKey: "payment_collection_id",
alias: "payment_collection",
},
],
extends: [
{
serviceName: Modules.CART,
fieldAlias: {
payment_collection: "payment_collection_link.payment_collection",
},
relationship: {
serviceName: LINKS.CartPaymentCollection,
primaryKey: "cart_id",
foreignKey: "id",
alias: "payment_collection_link",
},
},
{
serviceName: Modules.PAYMENT,
fieldAlias: {
cart: "cart_link.cart",
},
relationship: {
serviceName: LINKS.CartPaymentCollection,
primaryKey: "payment_collection_id",
foreignKey: "id",
alias: "cart_link",
},
},
],
}

View File

@@ -0,0 +1,55 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const CartPromotion: ModuleJoinerConfig = {
serviceName: LINKS.CartPromotion,
isLink: true,
databaseConfig: {
tableName: "cart_promotion",
idPrefix: "cartpromo",
},
alias: [
{
name: ["cart_promotion", "cart_promotions"],
args: {
entity: "LinkCartPromotion",
},
},
],
primaryKeys: ["id", "cart_id", "promotion_id"],
relationships: [
{
serviceName: Modules.CART,
primaryKey: "id",
foreignKey: "cart_id",
alias: "cart",
},
{
serviceName: Modules.PROMOTION,
primaryKey: "id",
foreignKey: "promotion_id",
alias: "promotion",
},
],
extends: [
{
serviceName: Modules.CART,
relationship: {
serviceName: LINKS.CartPromotion,
primaryKey: "cart_id",
foreignKey: "id",
alias: "cart_link",
},
},
{
serviceName: Modules.PROMOTION,
relationship: {
serviceName: LINKS.CartPromotion,
primaryKey: "promotion_id",
foreignKey: "id",
alias: "promotion_link",
},
},
],
}

View File

@@ -0,0 +1,28 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
export const CartRegion: ModuleJoinerConfig = {
isLink: true,
isReadOnlyLink: true,
extends: [
{
serviceName: Modules.CART,
relationship: {
serviceName: Modules.REGION,
primaryKey: "id",
foreignKey: "region_id",
alias: "region",
},
},
{
serviceName: Modules.REGION,
relationship: {
serviceName: Modules.CART,
primaryKey: "region_id",
foreignKey: "id",
alias: "carts",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,28 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
export const CartSalesChannel: ModuleJoinerConfig = {
isLink: true,
isReadOnlyLink: true,
extends: [
{
serviceName: Modules.CART,
relationship: {
serviceName: Modules.SALES_CHANNEL,
primaryKey: "id",
foreignKey: "sales_channel_id",
alias: "sales_channel",
},
},
{
serviceName: Modules.SALES_CHANNEL,
relationship: {
serviceName: Modules.CART,
primaryKey: "sales_channel_id",
foreignKey: "id",
alias: "carts",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,63 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const FulfillmentSetLocation: ModuleJoinerConfig = {
serviceName: LINKS.FulfillmentSetLocation,
isLink: true,
databaseConfig: {
tableName: "fulfillment_set_location",
idPrefix: "fsloc",
},
alias: [
{
name: ["fulfillment_set_location", "fulfillment_set_locations"],
args: {
entity: "LinkFulfillmentSetLocation",
},
},
],
primaryKeys: ["id", "fulfillment_set_id", "stock_location_id"],
relationships: [
{
serviceName: Modules.FULFILLMENT,
primaryKey: "id",
foreignKey: "fulfillment_set_id",
alias: "fulfillment_set",
},
{
serviceName: Modules.STOCK_LOCATION,
primaryKey: "id",
foreignKey: "stock_location_id",
alias: "location",
},
],
extends: [
{
serviceName: Modules.FULFILLMENT,
fieldAlias: {
stock_locations: "locations_link.location",
},
relationship: {
serviceName: LINKS.FulfillmentSetLocation,
primaryKey: "fulfillment_set_id",
foreignKey: "id",
alias: "locations_link",
isList: true,
},
},
{
serviceName: Modules.STOCK_LOCATION,
relationship: {
serviceName: LINKS.FulfillmentSetLocation,
primaryKey: "stock_location_id",
foreignKey: "id",
alias: "fulfillment_set_link",
isList: true,
},
fieldAlias: {
fulfillment_sets: "fulfillment_set_link.fulfillment_set",
},
},
],
}

View File

@@ -0,0 +1,20 @@
export * from "./cart-customer"
export * from "./cart-payment-collection"
export * from "./cart-promotion"
export * from "./cart-region"
export * from "./cart-sales-channel"
export * from "./fulfillment-set-location"
export * from "./inventory-level-stock-location"
export * from "./order-customer"
export * from "./order-promotion"
export * from "./order-region"
export * from "./order-sales-channel"
export * from "./product-sales-channel"
export * from "./product-shipping-profile"
export * from "./product-variant-inventory-item"
export * from "./product-variant-price-set"
export * from "./publishable-api-key-sales-channel"
export * from "./region-payment-provider"
export * from "./sales-channel-location"
export * from "./shipping-option-price-set"
export * from "./store-default-currency"

View File

@@ -0,0 +1,19 @@
import { ModuleJoinerConfig } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
export const InventoryLevelStockLocation: ModuleJoinerConfig = {
isLink: true,
isReadOnlyLink: true,
extends: [
{
serviceName: Modules.INVENTORY,
relationship: {
serviceName: Modules.STOCK_LOCATION,
primaryKey: "id",
foreignKey: "location_id",
alias: "stock_locations",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,28 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
export const OrderCustomer: ModuleJoinerConfig = {
isLink: true,
isReadOnlyLink: true,
extends: [
{
serviceName: Modules.ORDER,
relationship: {
serviceName: Modules.CUSTOMER,
primaryKey: "id",
foreignKey: "customer_id",
alias: "customer",
},
},
{
serviceName: Modules.CUSTOMER,
relationship: {
serviceName: Modules.ORDER,
primaryKey: "customer_id",
foreignKey: "id",
alias: "orders",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,55 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const OrderPromotion: ModuleJoinerConfig = {
serviceName: LINKS.OrderPromotion,
isLink: true,
databaseConfig: {
tableName: "order_promotion",
idPrefix: "orderpromo",
},
alias: [
{
name: ["order_promotion", "order_promotions"],
args: {
entity: "LinkOrderPromotion",
},
},
],
primaryKeys: ["id", "order_id", "promotion_id"],
relationships: [
{
serviceName: Modules.ORDER,
primaryKey: "id",
foreignKey: "order_id",
alias: "order",
},
{
serviceName: Modules.PROMOTION,
primaryKey: "id",
foreignKey: "promotion_id",
alias: "promotion",
},
],
extends: [
{
serviceName: Modules.ORDER,
relationship: {
serviceName: LINKS.OrderPromotion,
primaryKey: "order_id",
foreignKey: "id",
alias: "order_link",
},
},
{
serviceName: Modules.PROMOTION,
relationship: {
serviceName: LINKS.OrderPromotion,
primaryKey: "promotion_id",
foreignKey: "id",
alias: "promotion_link",
},
},
],
}

View File

@@ -0,0 +1,28 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
export const OrderRegion: ModuleJoinerConfig = {
isLink: true,
isReadOnlyLink: true,
extends: [
{
serviceName: Modules.ORDER,
relationship: {
serviceName: Modules.REGION,
primaryKey: "id",
foreignKey: "region_id",
alias: "region",
},
},
{
serviceName: Modules.REGION,
relationship: {
serviceName: Modules.ORDER,
primaryKey: "region_id",
foreignKey: "id",
alias: "orders",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,67 @@
import { ModuleJoinerConfig } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
import { LINKS } from "@medusajs/utils"
export const OrderSalesChannel: ModuleJoinerConfig = {
serviceName: LINKS.OrderSalesChannel,
isLink: true,
databaseConfig: {
tableName: "order_sales_channel",
idPrefix: "ordersc",
},
alias: [
{
name: "order_sales_channel",
},
{
name: "order_sales_channels",
},
],
primaryKeys: ["id", "order_id", "sales_channel_id"],
relationships: [
{
serviceName: Modules.ORDER,
isInternalService: true,
primaryKey: "id",
foreignKey: "order_id",
alias: "order",
},
{
serviceName: "salesChannelService",
isInternalService: true,
primaryKey: "id",
foreignKey: "sales_channel_id",
alias: "sales_channel",
},
],
extends: [
{
serviceName: Modules.ORDER,
fieldAlias: {
sales_channel: "sales_channel_link.sales_channel",
},
relationship: {
serviceName: LINKS.OrderSalesChannel,
isInternalService: true,
primaryKey: "order_id",
foreignKey: "id",
alias: "sales_channel_link",
},
},
{
serviceName: "salesChannelService",
fieldAlias: {
orders: "order_link.order",
},
relationship: {
serviceName: LINKS.OrderSalesChannel,
isInternalService: true,
primaryKey: "sales_channel_id",
foreignKey: "id",
alias: "order_link",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,62 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const ProductSalesChannel: ModuleJoinerConfig = {
serviceName: LINKS.ProductSalesChannel,
isLink: true,
databaseConfig: {
tableName: "product_sales_channel",
idPrefix: "prodsc",
},
alias: [
{
name: "product_sales_channel",
},
{
name: "product_sales_channels",
},
],
primaryKeys: ["id", "product_id", "sales_channel_id"],
relationships: [
{
serviceName: Modules.PRODUCT,
primaryKey: "id",
foreignKey: "product_id",
alias: "product",
},
{
serviceName: Modules.SALES_CHANNEL,
isInternalService: true,
primaryKey: "id",
foreignKey: "sales_channel_id",
alias: "sales_channel",
},
],
extends: [
{
serviceName: Modules.PRODUCT,
fieldAlias: {
sales_channels: "sales_channels_link.sales_channel",
},
relationship: {
serviceName: LINKS.ProductSalesChannel,
primaryKey: "product_id",
foreignKey: "id",
alias: "sales_channels_link",
isList: true,
},
},
{
serviceName: Modules.SALES_CHANNEL,
relationship: {
serviceName: LINKS.ProductSalesChannel,
isInternalService: true,
primaryKey: "sales_channel_id",
foreignKey: "id",
alias: "products_link",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,60 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const ProductShippingProfile: ModuleJoinerConfig = {
serviceName: LINKS.ProductShippingProfile,
isLink: true,
databaseConfig: {
tableName: "product_shipping_profile",
idPrefix: "psprof",
},
alias: [
{
name: "product_shipping_profile",
args: {
entity: "LinkProductShippingProfile",
},
},
],
primaryKeys: ["id", "product_id", "profile_id"],
relationships: [
{
serviceName: Modules.PRODUCT,
primaryKey: "id",
foreignKey: "product_id",
alias: "product",
},
{
serviceName: "shippingProfileService",
isInternalService: true,
primaryKey: "id",
foreignKey: "profile_id",
alias: "profile",
},
],
extends: [
{
serviceName: Modules.PRODUCT,
fieldAlias: {
profile: "shipping_profile.profile",
},
relationship: {
serviceName: LINKS.ProductShippingProfile,
primaryKey: "product_id",
foreignKey: "id",
alias: "shipping_profile",
},
},
{
serviceName: "shippingProfileService",
relationship: {
serviceName: LINKS.ProductShippingProfile,
isInternalService: true,
primaryKey: "profile_id",
foreignKey: "id",
alias: "product_link",
},
},
],
}

View File

@@ -0,0 +1,75 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const ProductVariantInventoryItem: ModuleJoinerConfig = {
serviceName: LINKS.ProductVariantInventoryItem,
isLink: true,
databaseConfig: {
tableName: "product_variant_inventory_item",
idPrefix: "pvitem",
extraFields: {
required_quantity: {
type: "integer",
defaultValue: "1",
},
},
},
alias: [
{
name: [
"product_variant_inventory_item",
"product_variant_inventory_items",
],
args: {
entity: "LinkProductVariantInventoryItem",
},
},
],
primaryKeys: ["id", "variant_id", "inventory_item_id"],
relationships: [
{
serviceName: Modules.PRODUCT,
primaryKey: "id",
foreignKey: "variant_id",
alias: "variant",
args: {
methodSuffix: "Variants",
},
},
{
serviceName: Modules.INVENTORY,
primaryKey: "id",
foreignKey: "inventory_item_id",
alias: "inventory",
deleteCascade: true,
},
],
extends: [
{
serviceName: Modules.PRODUCT,
fieldAlias: {
inventory: "inventory_items.inventory",
},
relationship: {
serviceName: LINKS.ProductVariantInventoryItem,
primaryKey: "variant_id",
foreignKey: "id",
alias: "inventory_items",
isList: true,
},
},
{
serviceName: Modules.INVENTORY,
fieldAlias: {
variant: "variant_link.variant",
},
relationship: {
serviceName: LINKS.ProductVariantInventoryItem,
primaryKey: "inventory_item_id",
foreignKey: "id",
alias: "variant_link",
},
},
],
}

View File

@@ -0,0 +1,72 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const ProductVariantPriceSet: ModuleJoinerConfig = {
serviceName: LINKS.ProductVariantPriceSet,
isLink: true,
databaseConfig: {
tableName: "product_variant_price_set",
idPrefix: "pvps",
},
alias: [
{
name: ["product_variant_price_set", "product_variant_price_sets"],
args: {
entity: "LinkProductVariantPriceSet",
},
},
],
primaryKeys: ["id", "variant_id", "price_set_id"],
relationships: [
{
serviceName: Modules.PRODUCT,
// TODO: Remove this when product module is the default product service
isInternalService: true,
primaryKey: "id",
foreignKey: "variant_id",
alias: "variant",
args: {
methodSuffix: "Variants",
},
},
{
serviceName: Modules.PRICING,
primaryKey: "id",
foreignKey: "price_set_id",
alias: "price_set",
deleteCascade: true,
},
],
extends: [
{
serviceName: Modules.PRODUCT,
fieldAlias: {
price_set: "price_set_link.price_set",
prices: "price_set_link.price_set.prices",
calculated_price: {
path: "price_set_link.price_set.calculated_price",
forwardArgumentsOnPath: ["price_set_link.price_set"],
},
},
relationship: {
serviceName: LINKS.ProductVariantPriceSet,
primaryKey: "variant_id",
foreignKey: "id",
alias: "price_set_link",
},
},
{
serviceName: Modules.PRICING,
relationship: {
serviceName: LINKS.ProductVariantPriceSet,
primaryKey: "price_set_id",
foreignKey: "id",
alias: "variant_link",
},
fieldAlias: {
variant: "variant_link.variant",
},
},
],
}

View File

@@ -0,0 +1,63 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const PublishableApiKeySalesChannel: ModuleJoinerConfig = {
serviceName: LINKS.PublishableApiKeySalesChannel,
isLink: true,
databaseConfig: {
tableName: "publishable_api_key_sales_channel",
idPrefix: "pksc",
},
alias: [
{
name: [
"publishable_api_key_sales_channel",
"publishable_api_key_sales_channels",
],
},
],
primaryKeys: ["id", "publishable_key_id", "sales_channel_id"],
relationships: [
{
serviceName: Modules.API_KEY,
primaryKey: "id",
foreignKey: "publishable_key_id",
alias: "api_key",
},
{
serviceName: Modules.SALES_CHANNEL,
primaryKey: "id",
foreignKey: "sales_channel_id",
alias: "sales_channel",
},
],
extends: [
{
serviceName: Modules.API_KEY,
fieldAlias: {
sales_channels: "sales_channels_link.sales_channel",
},
relationship: {
serviceName: LINKS.PublishableApiKeySalesChannel,
primaryKey: "publishable_key_id",
foreignKey: "id",
alias: "sales_channels_link",
isList: true,
},
},
{
serviceName: Modules.SALES_CHANNEL,
fieldAlias: {
publishable_api_keys: "api_keys_link.api_key",
},
relationship: {
serviceName: LINKS.PublishableApiKeySalesChannel,
primaryKey: "sales_channel_id",
foreignKey: "id",
alias: "api_keys_link",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,64 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const RegionPaymentProvider: ModuleJoinerConfig = {
serviceName: LINKS.RegionPaymentProvider,
isLink: true,
databaseConfig: {
tableName: "region_payment_provider",
idPrefix: "regpp",
},
alias: [
{
name: ["region_payment_provider", "region_payment_providers"],
args: {
entity: "LinkRegionPaymentProvider",
},
},
],
primaryKeys: ["id", "region_id", "payment_provider_id"],
relationships: [
{
serviceName: Modules.REGION,
primaryKey: "id",
foreignKey: "region_id",
alias: "region",
},
{
serviceName: Modules.PAYMENT,
primaryKey: "id",
foreignKey: "payment_provider_id",
alias: "payment_provider",
args: { methodSuffix: "PaymentProviders" },
},
],
extends: [
{
serviceName: Modules.REGION,
fieldAlias: {
payment_providers: "payment_provider_link.payment_provider",
},
relationship: {
serviceName: LINKS.RegionPaymentProvider,
primaryKey: "region_id",
foreignKey: "id",
alias: "payment_provider_link",
isList: true,
},
},
{
serviceName: Modules.PAYMENT,
fieldAlias: {
regions: "region_link.region",
},
relationship: {
serviceName: LINKS.RegionPaymentProvider,
primaryKey: "payment_provider_id",
foreignKey: "id",
alias: "region_link",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,63 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const SalesChannelLocation: ModuleJoinerConfig = {
serviceName: LINKS.SalesChannelLocation,
isLink: true,
databaseConfig: {
tableName: "sales_channel_stock_location",
idPrefix: "scloc",
},
alias: [
{
name: ["sales_channel_location", "sales_channel_locations"],
args: {
entity: "LinkSalesChannelLocation",
},
},
],
primaryKeys: ["id", "sales_channel_id", "stock_location_id"],
relationships: [
{
serviceName: Modules.SALES_CHANNEL,
primaryKey: "id",
foreignKey: "sales_channel_id",
alias: "sales_channel",
},
{
serviceName: Modules.STOCK_LOCATION,
primaryKey: "id",
foreignKey: "stock_location_id",
alias: "location",
},
],
extends: [
{
serviceName: Modules.SALES_CHANNEL,
fieldAlias: {
stock_locations: "locations_link.location",
},
relationship: {
serviceName: LINKS.SalesChannelLocation,
primaryKey: "sales_channel_id",
foreignKey: "id",
alias: "locations_link",
isList: true,
},
},
{
serviceName: Modules.STOCK_LOCATION,
fieldAlias: {
sales_channels: "sales_channels_link.sales_channel",
},
relationship: {
serviceName: LINKS.SalesChannelLocation,
primaryKey: "stock_location_id",
foreignKey: "id",
alias: "sales_channels_link",
isList: true,
},
},
],
}

View File

@@ -0,0 +1,72 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const ShippingOptionPriceSet: ModuleJoinerConfig = {
serviceName: LINKS.ShippingOptionPriceSet,
isLink: true,
databaseConfig: {
tableName: "shipping_option_price_set",
idPrefix: "sops",
},
alias: [
{
name: ["shipping_option_price_set", "shipping_option_price_sets"],
args: {
entity: "LinkShippingOptionPriceSet",
},
},
],
primaryKeys: ["id", "shipping_option_id", "price_set_id"],
relationships: [
{
serviceName: Modules.FULFILLMENT,
primaryKey: "id",
foreignKey: "shipping_option_id",
alias: "shipping_option",
args: {
methodSuffix: "ShippingOptions",
},
},
{
serviceName: Modules.PRICING,
primaryKey: "id",
foreignKey: "price_set_id",
alias: "price_set",
deleteCascade: true,
},
],
extends: [
{
serviceName: Modules.FULFILLMENT,
fieldAlias: {
prices: {
path: "price_set_link.price_set.prices",
isList: true,
},
calculated_price: {
path: "price_set_link.price_set.calculated_price",
forwardArgumentsOnPath: ["price_set_link.price_set"],
},
},
relationship: {
serviceName: LINKS.ShippingOptionPriceSet,
primaryKey: "shipping_option_id",
foreignKey: "id",
alias: "price_set_link",
},
},
{
serviceName: Modules.PRICING,
relationship: {
serviceName: LINKS.ShippingOptionPriceSet,
primaryKey: "price_set_id",
foreignKey: "id",
alias: "shipping_option_link",
},
fieldAlias: {
shipping_option: "shipping_option_link.shipping_option",
},
},
],
}

View File

@@ -0,0 +1,18 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
export const StoreDefaultCurrency: ModuleJoinerConfig = {
isLink: true,
isReadOnlyLink: true,
extends: [
{
serviceName: Modules.STORE,
relationship: {
serviceName: Modules.CURRENCY,
primaryKey: "code",
foreignKey: "default_currency_code",
alias: "default_currency",
},
},
],
}

View File

@@ -0,0 +1,5 @@
export * from "./initialize"
export * from "./types"
export * from "./loaders"
export * from "./services"
export * from "./utils/compose-link-name"

View File

@@ -0,0 +1,221 @@
import {
InternalModuleDeclaration,
MedusaModule,
ModuleRegistrationName,
} from "@medusajs/modules-sdk"
import {
ExternalModuleDeclaration,
ILinkModule,
LinkModuleDefinition,
LoaderOptions,
MODULE_RESOURCE_TYPE,
MODULE_SCOPE,
ModuleExports,
ModuleJoinerConfig,
ModuleServiceInitializeCustomDataLayerOptions,
ModuleServiceInitializeOptions,
} from "@medusajs/types"
import {
ContainerRegistrationKeys,
lowerCaseFirst,
simpleHash,
toPascalCase,
} from "@medusajs/utils"
import * as linkDefinitions from "../definitions"
import { getMigration } from "../migration"
import { InitializeModuleInjectableDependencies } from "../types"
import {
composeLinkName,
composeTableName,
generateGraphQLSchema,
} from "../utils"
import { getLinkModuleDefinition } from "./module-definition"
export const initialize = async (
options?:
| ModuleServiceInitializeOptions
| ModuleServiceInitializeCustomDataLayerOptions
| ExternalModuleDeclaration
| InternalModuleDeclaration,
modulesDefinition?: ModuleJoinerConfig[],
injectedDependencies?: InitializeModuleInjectableDependencies
): Promise<{ [link: string]: ILinkModule }> => {
const allLinks = {}
const modulesLoadedKeys = MedusaModule.getLoadedModules().map(
(mod) => Object.keys(mod)[0]
)
const allLinksToLoad = Object.values(linkDefinitions).concat(
modulesDefinition ?? []
)
for (const linkDefinition of allLinksToLoad) {
const definition = JSON.parse(JSON.stringify(linkDefinition))
const [primary, foreign] = definition.relationships ?? []
if (definition.relationships?.length !== 2 && !definition.isReadOnlyLink) {
throw new Error(
`Link module ${definition.serviceName} can only link 2 modules.`
)
} else if (
foreign?.foreignKey?.split(",").length > 1 &&
!definition.isReadOnlyLink
) {
throw new Error(`Foreign key cannot be a composed key.`)
}
const serviceKey = !definition.isReadOnlyLink
? lowerCaseFirst(
definition.serviceName ??
composeLinkName(
primary.serviceName,
primary.foreignKey,
foreign.serviceName,
foreign.foreignKey
)
)
: simpleHash(JSON.stringify(definition.extends))
if (modulesLoadedKeys.includes(serviceKey)) {
continue
} else if (serviceKey in allLinks) {
throw new Error(`Link module ${serviceKey} already defined.`)
}
if (definition.isReadOnlyLink) {
const extended: any[] = []
for (const extension of definition.extends ?? []) {
if (
modulesLoadedKeys.includes(extension.serviceName) &&
modulesLoadedKeys.includes(extension.relationship.serviceName)
) {
extended.push(extension)
}
}
definition.extends = extended
if (extended.length === 0) {
continue
}
} else if (
(!primary.isInternalService &&
!modulesLoadedKeys.includes(primary.serviceName)) ||
(!foreign.isInternalService &&
!modulesLoadedKeys.includes(foreign.serviceName))
) {
continue
}
const logger =
injectedDependencies?.[ContainerRegistrationKeys.LOGGER] ?? console
definition.schema = generateGraphQLSchema(definition, primary, foreign, {
logger,
})
definition.alias ??= []
for (const alias of definition.alias) {
alias.args ??= {}
alias.args.entity = toPascalCase(
"Link_" +
(definition.databaseConfig?.tableName ??
composeTableName(
primary.serviceName,
primary.foreignKey,
foreign.serviceName,
foreign.foreignKey
))
)
}
const moduleDefinition = getLinkModuleDefinition(
definition,
primary,
foreign
) as ModuleExports
const linkModuleDefinition: LinkModuleDefinition = {
key: serviceKey,
registrationName: serviceKey,
label: serviceKey,
dependencies: [ModuleRegistrationName.EVENT_BUS],
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: injectedDependencies?.[
ContainerRegistrationKeys.PG_CONNECTION
]
? MODULE_RESOURCE_TYPE.SHARED
: MODULE_RESOURCE_TYPE.ISOLATED,
},
}
const loaded = await MedusaModule.bootstrapLink({
definition: linkModuleDefinition,
declaration: options as InternalModuleDeclaration,
moduleExports: moduleDefinition,
injectedDependencies,
})
allLinks[serviceKey as string] = Object.values(loaded)[0]
}
return allLinks
}
export async function runMigrations(
{
options,
logger,
}: Omit<LoaderOptions<ModuleServiceInitializeOptions>, "container">,
modulesDefinition?: ModuleJoinerConfig[]
) {
const modulesLoadedKeys = MedusaModule.getLoadedModules().map(
(mod) => Object.keys(mod)[0]
)
const allLinksToLoad = Object.values(linkDefinitions).concat(
modulesDefinition ?? []
)
const allLinks = new Set<string>()
for (const definition of allLinksToLoad) {
if (definition.isReadOnlyLink) {
continue
}
if (definition.relationships?.length !== 2) {
throw new Error(
`Link module ${definition.serviceName} must have 2 relationships.`
)
}
const [primary, foreign] = definition.relationships ?? []
const serviceKey = lowerCaseFirst(
definition.serviceName ??
composeLinkName(
primary.serviceName,
primary.foreignKey,
foreign.serviceName,
foreign.foreignKey
)
)
if (allLinks.has(serviceKey)) {
throw new Error(`Link module ${serviceKey} already exists.`)
}
allLinks.add(serviceKey)
if (
!modulesLoadedKeys.includes(primary.serviceName) ||
!modulesLoadedKeys.includes(foreign.serviceName)
) {
continue
}
const migrate = getMigration(definition, serviceKey, primary, foreign)
await migrate({ options, logger })
}
}

View File

@@ -0,0 +1,24 @@
import {
JoinerRelationship,
ModuleExports,
ModuleJoinerConfig,
} from "@medusajs/types"
import { getModuleService, getReadOnlyModuleService } from "@services"
import { getLoaders } from "../loaders"
export function getLinkModuleDefinition(
joinerConfig: ModuleJoinerConfig,
primary: JoinerRelationship,
foreign: JoinerRelationship
): ModuleExports {
return {
service: joinerConfig.isReadOnlyLink
? getReadOnlyModuleService(joinerConfig)
: getModuleService(joinerConfig),
loaders: getLoaders({
joinerConfig,
primary,
foreign,
}),
}
}

View File

@@ -0,0 +1,35 @@
import {
InternalModuleDeclaration,
LoaderOptions,
ModuleServiceInitializeCustomDataLayerOptions,
ModuleServiceInitializeOptions,
} from "@medusajs/modules-sdk"
import { ModulesSdkUtils } from "@medusajs/utils"
import { EntitySchema } from "@mikro-orm/core"
export function connectionLoader(entity: EntitySchema) {
return async (
{
options,
container,
logger,
}: LoaderOptions<
| ModuleServiceInitializeOptions
| ModuleServiceInitializeCustomDataLayerOptions
>,
moduleDeclaration?: InternalModuleDeclaration
): Promise<void> => {
const pathToMigrations = __dirname + "/../migrations"
await ModulesSdkUtils.mikroOrmConnectionLoader({
moduleName: "link_module",
entities: [entity],
container,
options,
moduleDeclaration,
logger,
pathToMigrations,
})
}
}

View File

@@ -0,0 +1,67 @@
import { BaseRepository, getLinkRepository } from "@repositories"
import { LinkService, getModuleService } from "@services"
import { LoaderOptions } from "@medusajs/modules-sdk"
import {
InternalModuleDeclaration,
ModuleJoinerConfig,
ModulesSdkTypes,
} from "@medusajs/types"
import { lowerCaseFirst, simpleHash, toPascalCase } from "@medusajs/utils"
import { asClass, asValue } from "awilix"
import { composeLinkName, composeTableName } from "../utils"
export function containerLoader(entity, joinerConfig: ModuleJoinerConfig) {
return async (
{
options,
container,
}: LoaderOptions<
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
>,
moduleDeclaration?: InternalModuleDeclaration
): Promise<void> => {
const [primary, foreign] = joinerConfig.relationships!
const serviceName = !joinerConfig.isReadOnlyLink
? lowerCaseFirst(
joinerConfig.serviceName ??
composeLinkName(
primary.serviceName,
primary.foreignKey,
foreign.serviceName,
foreign.foreignKey
)
)
: simpleHash(JSON.stringify(joinerConfig.extends))
const entityName = toPascalCase(
"Link_" +
(joinerConfig.databaseConfig?.tableName ??
composeTableName(
primary.serviceName,
primary.foreignKey,
foreign.serviceName,
foreign.foreignKey
))
)
container.register({
joinerConfig: asValue(joinerConfig),
primaryKey: asValue(primary.foreignKey.split(",")),
foreignKey: asValue(foreign.foreignKey),
extraFields: asValue(
Object.keys(joinerConfig.databaseConfig?.extraFields || {})
),
linkModuleService: asClass(getModuleService(joinerConfig)).singleton(),
linkService: asClass(LinkService).singleton(),
baseRepository: asClass(BaseRepository).singleton(),
linkRepository: asClass(getLinkRepository(entity)).singleton(),
entityName: asValue(entityName),
serviceName: asValue(serviceName),
})
}
}

View File

@@ -0,0 +1,26 @@
import {
JoinerRelationship,
ModuleJoinerConfig,
ModuleLoaderFunction,
} from "@medusajs/types"
import { generateEntity } from "../utils"
import { connectionLoader } from "./connection"
import { containerLoader } from "./container"
export function getLoaders({
joinerConfig,
primary,
foreign,
}: {
joinerConfig: ModuleJoinerConfig
primary: JoinerRelationship
foreign: JoinerRelationship
}): ModuleLoaderFunction[] {
if (joinerConfig.isReadOnlyLink) {
return []
}
const entity = generateEntity(joinerConfig, primary, foreign)
return [connectionLoader(entity), containerLoader(entity, joinerConfig)]
}

View File

@@ -0,0 +1,86 @@
import {
JoinerRelationship,
LoaderOptions,
Logger,
ModuleJoinerConfig,
ModuleServiceInitializeOptions,
} from "@medusajs/types"
import { generateEntity } from "../utils"
import { DALUtils, ModulesSdkUtils } from "@medusajs/utils"
export function getMigration(
joinerConfig: ModuleJoinerConfig,
serviceName: string,
primary: JoinerRelationship,
foreign: JoinerRelationship
) {
return async function runMigrations(
{
options,
logger,
}: Pick<
LoaderOptions<ModuleServiceInitializeOptions>,
"options" | "logger"
> = {} as any
) {
logger ??= console as unknown as Logger
const dbData = ModulesSdkUtils.loadDatabaseConfig("link_modules", options)
const entity = generateEntity(joinerConfig, primary, foreign)
const pathToMigrations = __dirname + "/../migrations"
const orm = await DALUtils.mikroOrmCreateConnection(
dbData,
[entity],
pathToMigrations
)
const tableName = entity.meta.collection
let hasTable = false
try {
await orm.em.getConnection().execute(`SELECT 1 FROM ${tableName} LIMIT 0`)
hasTable = true
} catch {}
const generator = orm.getSchemaGenerator()
if (hasTable) {
/* const updateSql = await generator.getUpdateSchemaSQL()
const entityUpdates = updateSql
.split(";")
.map((sql) => sql.trim())
.filter((sql) =>
sql.toLowerCase().includes(`alter table "${tableName.toLowerCase()}"`)
)
if (entityUpdates.length > 0) {
try {
await generator.execute(entityUpdates.join(";"))
logger.info(`Link module "${serviceName}" migration executed`)
} catch (error) {
logger.error(
`Link module "${serviceName}" migration failed to run - Error: ${error}`
)
}
} else {
logger.info(`Skipping "${tableName}" migration.`)
}*/
logger.info(
`Link module "${serviceName}" table update skipped because the table already exists. Please write your own migration if needed.`
)
} else {
try {
await generator.createSchema()
logger.info(`Link module "${serviceName}" migration executed`)
} catch (error) {
logger.error(
`Link module "${serviceName}" migration failed to run - Error: ${error}`
)
}
}
await orm.close()
}
}

View File

@@ -0,0 +1,2 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
export { getLinkRepository } from "./link"

View File

@@ -0,0 +1,49 @@
import { Context, ModuleJoinerConfig } from "@medusajs/types"
import { EntitySchema } from "@mikro-orm/core"
import {
generateEntityId,
mikroOrmBaseRepositoryFactory,
} from "@medusajs/utils"
import { SqlEntityManager } from "@mikro-orm/postgresql"
export function getLinkRepository(model: EntitySchema) {
return class LinkRepository extends mikroOrmBaseRepositoryFactory(model) {
readonly joinerConfig_: ModuleJoinerConfig
constructor({ joinerConfig }: { joinerConfig: ModuleJoinerConfig }) {
// @ts-ignore
super(...arguments)
this.joinerConfig_ = joinerConfig
}
async delete(data: any, context: Context = {}): Promise<void> {
const filter = {}
for (const key in data) {
filter[key] = {
$in: Array.isArray(data[key]) ? data[key] : [data[key]],
}
}
const manager = this.getActiveManager<SqlEntityManager>(context)
await manager.nativeDelete(model, data, {})
}
async create(data: object[], context: Context = {}): Promise<object[]> {
const manager = this.getActiveManager<SqlEntityManager>(context)
const links = data.map((link: any) => {
link.id = generateEntityId(
link.id,
this.joinerConfig_.databaseConfig?.idPrefix ?? "link"
)
link.deleted_at = null
return manager.create(model, link)
})
await manager.upsertMany(model, links)
return links
}
}
}

View File

@@ -0,0 +1,22 @@
import { Constructor, ILinkModule, ModuleJoinerConfig } from "@medusajs/types"
import { LinkModuleService } from "@services"
export function getModuleService(
joinerConfig: ModuleJoinerConfig
): Constructor<ILinkModule> {
const joinerConfig_ = JSON.parse(JSON.stringify(joinerConfig))
delete joinerConfig_.databaseConfig
return class LinkService extends LinkModuleService<unknown> {
override __joinerConfig(): ModuleJoinerConfig {
return joinerConfig_ as ModuleJoinerConfig
}
}
}
export function getReadOnlyModuleService(joinerConfig: ModuleJoinerConfig) {
return class ReadOnlyLinkService {
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig as ModuleJoinerConfig
}
}
}

View File

@@ -0,0 +1,3 @@
export * from "./dynamic-service-class"
export { default as LinkService } from "./link"
export { default as LinkModuleService } from "./link-module-service"

View File

@@ -0,0 +1,397 @@
import {
Context,
DAL,
FindConfig,
IEventBusModuleService,
ILinkModule,
InternalModuleDeclaration,
ModuleJoinerConfig,
RestoreReturn,
SoftDeleteReturn,
} from "@medusajs/types"
import {
CommonEvents,
InjectManager,
InjectTransactionManager,
MapToConfig,
MedusaContext,
MedusaError,
ModulesSdkUtils,
isDefined,
mapObjectTo,
} from "@medusajs/utils"
import { LinkService } from "@services"
import { shouldForceTransaction } from "../utils"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
linkService: LinkService<any>
eventBusModuleService?: IEventBusModuleService
primaryKey: string | string[]
foreignKey: string
extraFields: string[]
entityName: string
serviceName: string
}
export default class LinkModuleService<TLink> implements ILinkModule {
protected baseRepository_: DAL.RepositoryService
protected readonly linkService_: LinkService<TLink>
protected readonly eventBusModuleService_?: IEventBusModuleService
protected readonly entityName_: string
protected readonly serviceName_: string
protected primaryKey_: string[]
protected foreignKey_: string
protected extraFields_: string[]
constructor(
{
baseRepository,
linkService,
eventBusModuleService,
primaryKey,
foreignKey,
extraFields,
entityName,
serviceName,
}: InjectedDependencies,
readonly moduleDeclaration: InternalModuleDeclaration
) {
this.baseRepository_ = baseRepository
this.linkService_ = linkService
this.eventBusModuleService_ = eventBusModuleService
this.primaryKey_ = !Array.isArray(primaryKey) ? [primaryKey] : primaryKey
this.foreignKey_ = foreignKey
this.extraFields_ = extraFields
this.entityName_ = entityName
this.serviceName_ = serviceName
}
__joinerConfig(): ModuleJoinerConfig {
return {} as ModuleJoinerConfig
}
private buildData(
primaryKeyData: string | string[],
foreignKeyData: string,
extra: Record<string, unknown> = {}
) {
if (this.primaryKey_.length > 1) {
if (
!Array.isArray(primaryKeyData) ||
primaryKeyData.length !== this.primaryKey_.length
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Primary key data must be an array ${this.primaryKey_.length} values`
)
}
}
const pk = this.primaryKey_.join(",")
return {
[pk]: primaryKeyData,
[this.foreignKey_]: foreignKeyData,
...extra,
}
}
private isValidKeyName(name: string) {
return this.primaryKey_.concat(this.foreignKey_).includes(name)
}
private validateFields(data: any | any[]) {
const dataToValidate = Array.isArray(data) ? data : [data]
dataToValidate.forEach((d) => {
const keys = Object.keys(d)
if (keys.some((k) => !this.isValidKeyName(k))) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Invalid field name provided. Valid field names are ${this.primaryKey_.concat(
this.foreignKey_
)}`
)
}
})
}
@InjectManager("baseRepository_")
async retrieve(
primaryKeyData: string | string[],
foreignKeyData: string,
@MedusaContext() sharedContext: Context = {}
): Promise<unknown> {
const filter = this.buildData(primaryKeyData, foreignKeyData)
const queryOptions = ModulesSdkUtils.buildQuery<unknown>(filter)
const entry = await this.linkService_.list(queryOptions, {}, sharedContext)
if (!entry?.length) {
const pk = this.primaryKey_.join(",")
const errMessage = `${pk}[${primaryKeyData}] and ${this.foreignKey_}[${foreignKeyData}]`
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Entry ${errMessage} was not found`
)
}
return entry[0]
}
@InjectManager("baseRepository_")
async list(
filters: Record<string, unknown> = {},
config: FindConfig<unknown> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<unknown[]> {
if (!isDefined(config.take)) {
config.take = null
}
const rows = await this.linkService_.list(filters, config, sharedContext)
return await this.baseRepository_.serialize<object[]>(rows)
}
@InjectManager("baseRepository_")
async listAndCount(
filters: Record<string, unknown> = {},
config: FindConfig<unknown> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[unknown[], number]> {
if (!isDefined(config.take)) {
config.take = null
}
const [rows, count] = await this.linkService_.listAndCount(
filters,
config,
sharedContext
)
return [await this.baseRepository_.serialize<object[]>(rows), count]
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async create(
primaryKeyOrBulkData:
| string
| string[]
| [string | string[], string, Record<string, unknown>][],
foreignKeyData?: string,
extraFields?: Record<string, unknown>,
@MedusaContext() sharedContext: Context = {}
) {
const data: unknown[] = []
if (foreignKeyData === undefined && Array.isArray(primaryKeyOrBulkData)) {
for (const [primaryKey, foreignKey, extra] of primaryKeyOrBulkData) {
data.push(
this.buildData(
primaryKey as string | string[],
foreignKey as string,
extra as Record<string, unknown>
)
)
}
} else {
data.push(
this.buildData(
primaryKeyOrBulkData as string | string[],
foreignKeyData!,
extraFields
)
)
}
const links = await this.linkService_.create(data, sharedContext)
await this.eventBusModuleService_?.emit<Record<string, unknown>>(
(data as { id: unknown }[]).map(({ id }) => ({
eventName: this.entityName_ + "." + CommonEvents.ATTACHED,
body: {
metadata: {
service: this.serviceName_,
action: CommonEvents.ATTACHED,
object: this.entityName_,
eventGroupId: sharedContext.eventGroupId,
},
data: { id },
},
}))
)
return await this.baseRepository_.serialize<object[]>(links)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async dismiss(
primaryKeyOrBulkData: string | string[] | [string | string[], string][],
foreignKeyData?: string,
@MedusaContext() sharedContext: Context = {}
) {
const data: unknown[] = []
if (foreignKeyData === undefined && Array.isArray(primaryKeyOrBulkData)) {
for (const [primaryKey, foreignKey] of primaryKeyOrBulkData) {
data.push(this.buildData(primaryKey, foreignKey as string))
}
} else {
data.push(
this.buildData(
primaryKeyOrBulkData as string | string[],
foreignKeyData!
)
)
}
const links = await this.linkService_.dismiss(data, sharedContext)
return await this.baseRepository_.serialize<object[]>(links)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async delete(
data: any,
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
this.validateFields(data)
await this.linkService_.delete(data, sharedContext)
const allData = Array.isArray(data) ? data : [data]
await this.eventBusModuleService_?.emit<Record<string, unknown>>(
allData.map(({ id }) => ({
eventName: this.entityName_ + "." + CommonEvents.DETACHED,
body: {
metadata: {
service: this.serviceName_,
action: CommonEvents.DETACHED,
object: this.entityName_,
eventGroupId: sharedContext.eventGroupId,
},
data: { id },
},
}))
)
}
async softDelete(
data: any,
{ returnLinkableKeys }: SoftDeleteReturn = {},
@MedusaContext() sharedContext: Context = {}
): Promise<Record<string, unknown[]> | void> {
const inputArray = Array.isArray(data) ? data : [data]
this.validateFields(inputArray)
let [deletedEntities, cascadedEntitiesMap] = await this.softDelete_(
inputArray,
sharedContext
)
const pk = this.primaryKey_.join(",")
const entityNameToLinkableKeysMap: MapToConfig = {
LinkModel: [
{ mapTo: pk, valueFrom: pk },
{ mapTo: this.foreignKey_, valueFrom: this.foreignKey_ },
],
}
let mappedCascadedEntitiesMap
if (returnLinkableKeys) {
// Map internal table/column names to their respective external linkable keys
// eg: product.id = product_id, variant.id = variant_id
mappedCascadedEntitiesMap = mapObjectTo<Record<string, string[]>>(
cascadedEntitiesMap,
entityNameToLinkableKeysMap,
{
pick: returnLinkableKeys,
}
)
}
await this.eventBusModuleService_?.emit<Record<string, unknown>>(
(deletedEntities as { id: string }[]).map(({ id }) => ({
eventName: this.entityName_ + "." + CommonEvents.DETACHED,
body: {
metadata: {
service: this.serviceName_,
action: CommonEvents.DETACHED,
object: this.entityName_,
eventGroupId: sharedContext.eventGroupId,
},
data: { id },
},
}))
)
return mappedCascadedEntitiesMap ? mappedCascadedEntitiesMap : void 0
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
protected async softDelete_(
data: any[],
@MedusaContext() sharedContext: Context = {}
): Promise<[object[], Record<string, string[]>]> {
return await this.linkService_.softDelete(data, sharedContext)
}
async restore(
data: any,
{ returnLinkableKeys }: RestoreReturn = {},
@MedusaContext() sharedContext: Context = {}
): Promise<Record<string, unknown[]> | void> {
const inputArray = Array.isArray(data) ? data : [data]
this.validateFields(inputArray)
let [restoredEntities, cascadedEntitiesMap] = await this.restore_(
inputArray,
sharedContext
)
const pk = this.primaryKey_.join(",")
const entityNameToLinkableKeysMap: MapToConfig = {
LinkModel: [
{ mapTo: pk, valueFrom: pk },
{ mapTo: this.foreignKey_, valueFrom: this.foreignKey_ },
],
}
let mappedCascadedEntitiesMap
if (returnLinkableKeys) {
// Map internal table/column names to their respective external linkable keys
// eg: product.id = product_id, variant.id = variant_id
mappedCascadedEntitiesMap = mapObjectTo<Record<string, string[]>>(
cascadedEntitiesMap,
entityNameToLinkableKeysMap,
{
pick: returnLinkableKeys,
}
)
}
await this.eventBusModuleService_?.emit<Record<string, unknown>>(
(restoredEntities as { id: string }[]).map(({ id }) => ({
eventName: this.entityName_ + "." + CommonEvents.ATTACHED,
body: {
metadata: {
service: this.serviceName_,
action: CommonEvents.ATTACHED,
object: this.entityName_,
eventGroupId: sharedContext.eventGroupId,
},
data: { id },
},
}))
)
return mappedCascadedEntitiesMap ? mappedCascadedEntitiesMap : void 0
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async restore_(
data: any,
@MedusaContext() sharedContext: Context = {}
): Promise<[object[], Record<string, string[]>]> {
return await this.linkService_.restore(data, sharedContext)
}
}

View File

@@ -0,0 +1,135 @@
import { Context, FindConfig } from "@medusajs/types"
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
ModulesSdkUtils,
} from "@medusajs/utils"
import { doNotForceTransaction } from "../utils"
type InjectedDependencies = {
linkRepository: any
}
export default class LinkService<TEntity> {
protected readonly linkRepository_: any
constructor({ linkRepository }: InjectedDependencies) {
this.linkRepository_ = linkRepository
}
@InjectManager("linkRepository_")
async list(
filters: unknown = {},
config: FindConfig<unknown> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
const queryOptions = ModulesSdkUtils.buildQuery<unknown>(
filters as any,
config
)
return await this.linkRepository_.find(queryOptions, sharedContext)
}
@InjectManager("linkRepository_")
async listAndCount(
filters = {},
config: FindConfig<unknown> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[TEntity[], number]> {
const queryOptions = ModulesSdkUtils.buildQuery<unknown>(filters, config)
return await this.linkRepository_.findAndCount(queryOptions, sharedContext)
}
@InjectTransactionManager(doNotForceTransaction, "linkRepository_")
async create(
data: unknown[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
return await this.linkRepository_.create(data, {
transactionManager: sharedContext.transactionManager,
})
}
@InjectTransactionManager(doNotForceTransaction, "linkRepository_")
async dismiss(
data: unknown[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
const filter: any = []
for (const pair of data) {
filter.push({
$and: Object.entries(pair as object).map(([key, value]) => ({
[key]: value,
})),
})
}
const [rows] = await this.linkRepository_.softDelete(
{ $or: filter },
{
transactionManager: sharedContext.transactionManager,
}
)
return rows
}
@InjectTransactionManager(doNotForceTransaction, "linkRepository_")
async delete(
data: unknown,
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.linkRepository_.delete(data, {
transactionManager: sharedContext.transactionManager,
})
}
@InjectTransactionManager(doNotForceTransaction, "linkRepository_")
async softDelete(
data: any[],
@MedusaContext() sharedContext: Context = {}
): Promise<[object[], Record<string, string[]>]> {
const deleteFilters = {
$or: data.map((dataEntry) => {
const filter = {}
for (const key in dataEntry) {
filter[key] = {
$in: Array.isArray(dataEntry[key])
? dataEntry[key]
: [dataEntry[key]],
}
}
return filter
}),
}
return await this.linkRepository_.softDelete(deleteFilters, {
transactionManager: sharedContext.transactionManager,
})
}
@InjectTransactionManager(doNotForceTransaction, "linkRepository_")
async restore(
data: any,
@MedusaContext() sharedContext: Context = {}
): Promise<[object[], Record<string, string[]>]> {
const restoreFilters = {
$or: data.map((dataEntry) => {
const filter = {}
for (const key in dataEntry) {
filter[key] = {
$in: Array.isArray(dataEntry[key])
? dataEntry[key]
: [dataEntry[key]],
}
}
return filter
}),
}
return await this.linkRepository_.restore(restoreFilters, {
transactionManager: sharedContext.transactionManager,
})
}
}

View File

@@ -0,0 +1,5 @@
import { Logger } from "@medusajs/types"
export type InitializeModuleInjectableDependencies = {
logger?: Logger
}

View File

@@ -0,0 +1,9 @@
import { lowerCaseFirst, toPascalCase } from "@medusajs/utils"
export const composeLinkName = (...args) => {
return lowerCaseFirst(toPascalCase(composeTableName(...args.concat("link"))))
}
export const composeTableName = (...args) => {
return args.map((name) => name.replace(/(_id|Service)$/gi, "")).join("_")
}

View File

@@ -0,0 +1,117 @@
import { JoinerRelationship, ModuleJoinerConfig } from "@medusajs/types"
import {
SoftDeletableFilterKey,
mikroOrmSoftDeletableFilterOptions,
simpleHash,
} from "@medusajs/utils"
import { EntitySchema } from "@mikro-orm/core"
import { composeTableName } from "./compose-link-name"
function getClass(...properties) {
return class LinkModel {
constructor(...values) {
properties.forEach((name, idx) => {
this[name] = values[idx]
})
}
}
}
export function generateEntity(
joinerConfig: ModuleJoinerConfig,
primary: JoinerRelationship,
foreign: JoinerRelationship
) {
const fieldNames = primary.foreignKey.split(",").concat(foreign.foreignKey)
const tableName =
joinerConfig.databaseConfig?.tableName ??
composeTableName(
primary.serviceName,
primary.foreignKey,
foreign.serviceName,
foreign.foreignKey
)
const fields = fieldNames.reduce((acc, curr) => {
acc[curr] = {
type: "string",
nullable: false,
primary: true,
}
return acc
}, {})
const extraFields = joinerConfig.databaseConfig?.extraFields ?? {}
for (const column in extraFields) {
fieldNames.push(column)
fields[column] = {
type: extraFields[column].type,
nullable: !!extraFields[column].nullable,
defaultRaw: extraFields[column].defaultValue,
...(extraFields[column].options ?? {}),
}
}
const hashTableName = simpleHash(tableName)
return new EntitySchema({
class: getClass(
...fieldNames.concat("created_at", "updated_at", "deleted_at")
) as any,
tableName,
properties: {
id: {
type: "string",
nullable: false,
},
...fields,
created_at: {
type: "Date",
nullable: false,
defaultRaw: "CURRENT_TIMESTAMP",
},
updated_at: {
type: "Date",
nullable: false,
defaultRaw: "CURRENT_TIMESTAMP",
},
deleted_at: { type: "Date", nullable: true },
},
filters: {
[SoftDeletableFilterKey]: mikroOrmSoftDeletableFilterOptions,
},
hooks: {
beforeUpdate: [
(args) => {
args.entity.updated_at = new Date()
},
],
},
indexes: [
{
properties: ["id"],
name: "IDX_id_" + hashTableName,
},
{
properties: primary.foreignKey.split(","),
name:
"IDX_" +
primary.foreignKey.split(",").join("_") +
"_" +
hashTableName,
},
{
properties: foreign.foreignKey,
name: "IDX_" + foreign.foreignKey + "_" + hashTableName,
},
{
properties: ["deleted_at"],
name: "IDX_deleted_at_" + hashTableName,
},
],
})
}

View File

@@ -0,0 +1,152 @@
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,
{ logger }: { logger } = { logger: console }
) {
let fieldNames!: string[]
let entityName!: string
if (!joinerConfig.isReadOnlyLink) {
fieldNames = primary.foreignKey.split(",").concat(foreign.foreignKey)
entityName = toPascalCase(
"Link_" +
(joinerConfig.databaseConfig?.tableName ??
composeTableName(
primary.serviceName,
primary.foreignKey,
foreign.serviceName,
foreign.foreignKey
))
)
}
let typeDef = ""
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 extJoinerConfig = MedusaModule.getJoinerConfig(
extend.relationship.serviceName
)
let extendedEntityName =
extJoinerConfig?.linkableKeys?.[extend.relationship.foreignKey]!
if (!extendedEntityName && (!primary || !foreign)) {
logger.warn(
`Link modules schema: No linkable key found for ${extend.relationship.foreignKey} on module ${extend.relationship.serviceName}.`
)
continue
}
const fieldName = camelToSnakeCase(
lowerCaseFirst(extend.relationship.alias)
)
let type = extend.relationship.isList ? `[${entityName}]` : entityName
if (extJoinerConfig?.isReadOnlyLink) {
type = extend.relationship.isList
? `[${extendedEntityName}]`
: extendedEntityName
}
typeDef += `
extend type ${extend.serviceName} {
${fieldName}: ${type}
}
`
}
if (joinerConfig.isReadOnlyLink) {
return typeDef
}
// 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)
)}`
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
}
`
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"
}

View File

@@ -0,0 +1,13 @@
import { MODULE_RESOURCE_TYPE } from "@medusajs/types"
export * from "./compose-link-name"
export * from "./generate-entity"
export * from "./generate-schema"
export function shouldForceTransaction(target: any): boolean {
return target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
}
export function doNotForceTransaction(): boolean {
return false
}