feat(link-modules,modules-sdk, utils, types, products) - Remote Link and Link modules (#4695)

What:
- Definition of all Modules links
- `link-modules` package to manage the creation of all pre-defined link or custom ones

```typescript
import { initialize as iniInventory } from "@medusajs/inventory";
import { initialize as iniProduct } from "@medusajs/product";

import {
  initialize as iniLinks,
  runMigrations as migrateLinks
} from "@medusajs/link-modules";

await Promise.all([iniInventory(), iniProduct()]);


await migrateLinks(); // create tables based on previous loaded modules

await iniLinks(); // load link based on previous loaded modules

await iniLinks(undefined, [
  {
    serviceName: "product_custom_translation_service_link",
    isLink: true,
    databaseConfig: {
      tableName: "product_transalations",
    },
    alias: [
      {
        name: "translations",
      },
    ],
    primaryKeys: ["id", "product_id", "translation_id"],
    relationships: [
      {
        serviceName: Modules.PRODUCT,
        primaryKey: "id",
        foreignKey: "product_id",
        alias: "product",
      },
      {
        serviceName: "custom_translation_service",
        primaryKey: "id",
        foreignKey: "translation_id",
        alias: "transalation",
        deleteCascade: true,
      },
    ],
    extends: [
      {
        serviceName: Modules.PRODUCT,
        relationship: {
          serviceName: "product_custom_translation_service_link",
          primaryKey: "product_id",
          foreignKey: "id",
          alias: "translations",
          isList: true,
        },
      },
      {
        serviceName: "custom_translation_service",
        relationship: {
          serviceName: "product_custom_translation_service_link",
          primaryKey: "product_id",
          foreignKey: "id",
          alias: "product_link",
        },
      },
    ],
  },
]); // custom links
```

Remote Link

```typescript
import { RemoteLink, Modules } from "@medusajs/modules-sdk";

// [...] initialize modules and links

const remoteLink = new RemoteLink();

// upsert the relationship
await remoteLink.create({ // one (object) or many (array)
  [Modules.PRODUCT]: {
    variant_id: "var_abc",
  },
  [Modules.INVENTORY]: {
    inventory_item_id: "iitem_abc",
  },
  data: { // optional additional fields
    required_quantity: 5
  }
});

// dismiss (doesn't cascade)
await remoteLink.dismiss({ // one (object) or many (array)
  [Modules.PRODUCT]: {
    variant_id: "var_abc",
  },
  [Modules.INVENTORY]: {
    inventory_item_id: "iitem_abc",
  },
});

// delete
await remoteLink.delete({
  // every key is a module
  [Modules.PRODUCT]: {
    // every key is a linkable field
    variant_id: "var_abc", // single or multiple values
  },
});

// restore
await remoteLink.restore({
  // every key is a module
  [Modules.PRODUCT]: {
    // every key is a linkable field
    variant_id: "var_abc", // single or multiple values
  },
});

```

Co-authored-by: Riqwan Thamir <5105988+riqwan@users.noreply.github.com>
This commit is contained in:
Carlos R. L. Rodrigues
2023-08-30 14:31:32 +00:00
committed by GitHub
co-authored by Riqwan Thamir
parent bc4c9e0d32
commit 4d16acf5f0
97 changed files with 3540 additions and 424 deletions
+3 -2
View File
@@ -1,13 +1,14 @@
import {
ExternalModuleDeclaration,
InternalModuleDeclaration,
MedusaModule,
MODULE_PACKAGE_NAMES,
MedusaModule,
Modules,
} from "@medusajs/modules-sdk"
import { IProductModuleService, ModulesSdkTypes } from "@medusajs/types"
import { moduleDefinition } from "../module-definition"
import { InitializeModuleInjectableDependencies } from "../types"
import { moduleDefinition } from "../module-definition"
export const initialize = async (
options?:
+5 -4
View File
@@ -1,5 +1,6 @@
import { Modules } from "@medusajs/modules-sdk"
import { JoinerServiceConfig } from "@medusajs/types"
import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import {
Product,
ProductCategory,
@@ -10,10 +11,9 @@ import {
ProductVariant,
} from "@models"
import ProductImage from "./models/product-image"
import { MapToConfig } from "@medusajs/utils"
export enum LinkableKeys {
PRODUCT_ID = "product_id",
PRODUCT_ID = "product_id", // Main service ID must the first
PRODUCT_HANDLE = "product_handle",
VARIANT_ID = "variant_id",
VARIANT_SKU = "variant_sku",
@@ -55,9 +55,10 @@ export const entityNameToLinkableKeysMap: MapToConfig = {
],
}
export const joinerConfig: JoinerServiceConfig = {
export const joinerConfig: ModuleJoinerConfig = {
serviceName: Modules.PRODUCT,
primaryKeys: ["id", "handle"],
linkableKeys: Object.values(LinkableKeys),
alias: [
{
name: "product",
@@ -1,12 +1,15 @@
import {
ProductCategoryService,
ProductCollectionService,
ProductOptionService,
ProductService,
ProductTagService,
ProductTypeService,
ProductVariantService,
} from "@services"
Context,
CreateProductOnlyDTO,
DAL,
FindConfig,
IEventBusModuleService,
InternalModuleDeclaration,
ModuleJoinerConfig,
ProductTypes,
RestoreReturn,
SoftDeleteReturn,
} from "@medusajs/types"
import {
Image,
Product,
@@ -18,15 +21,14 @@ import {
ProductVariant,
} from "@models"
import {
Context,
CreateProductOnlyDTO,
DAL,
FindConfig,
IEventBusModuleService,
InternalModuleDeclaration,
JoinerServiceConfig,
ProductTypes,
} from "@medusajs/types"
ProductCategoryService,
ProductCollectionService,
ProductOptionService,
ProductService,
ProductTagService,
ProductTypeService,
ProductVariantService,
} from "@services"
import ProductImageService from "./product-image"
@@ -136,7 +138,7 @@ export default class ProductModuleService<
this.eventBusModuleService_ = eventBusModuleService
}
__joinerConfig(): JoinerServiceConfig {
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig
}
@@ -1071,11 +1073,7 @@ export default class ProductModuleService<
>
>(
productIds: string[],
{
returnLinkableKeys,
}: { returnLinkableKeys?: TReturnableLinkableKeys[] } = {
returnLinkableKeys: [],
},
{ returnLinkableKeys }: SoftDeleteReturn<TReturnableLinkableKeys> = {},
sharedContext: Context = {}
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
const [products, cascadedEntitiesMap] = await this.softDelete_(
@@ -1098,10 +1096,12 @@ export default class ProductModuleService<
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<Lowercase<keyof typeof LinkableKeys>, string[]>
>(cascadedEntitiesMap, entityNameToLinkableKeysMap, {
pick: returnLinkableKeys as string[],
pick: returnLinkableKeys,
})
}
@@ -1116,22 +1116,39 @@ export default class ProductModuleService<
return await this.productService_.softDelete(productIds, sharedContext)
}
async restore(
async restore<
TReturnableLinkableKeys extends string = Lowercase<
keyof typeof LinkableKeys
>
>(
productIds: string[],
{ returnLinkableKeys }: RestoreReturn<TReturnableLinkableKeys> = {},
sharedContext: Context = {}
): Promise<ProductTypes.ProductDTO[]> {
const products = await this.restore_(productIds, sharedContext)
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
const [_, cascadedEntitiesMap] = await this.restore_(
productIds,
sharedContext
)
return this.baseRepository_.serialize<ProductTypes.ProductDTO[]>(products, {
populate: true,
})
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<Lowercase<keyof typeof LinkableKeys>, string[]>
>(cascadedEntitiesMap, entityNameToLinkableKeysMap, {
pick: returnLinkableKeys,
})
}
return mappedCascadedEntitiesMap ? mappedCascadedEntitiesMap : void 0
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async restore_(
productIds: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<TProduct[]> {
): Promise<[TProduct[], Record<string, unknown[]>]> {
return await this.productService_.restore(productIds, sharedContext)
}
}
+3 -3
View File
@@ -1,4 +1,3 @@
import { Product } from "@models"
import {
Context,
DAL,
@@ -9,12 +8,13 @@ import {
import {
InjectManager,
InjectTransactionManager,
isDefined,
MedusaContext,
MedusaError,
ModulesSdkUtils,
ProductUtils,
isDefined,
} from "@medusajs/utils"
import { Product } from "@models"
import { ProductRepository } from "@repositories"
import { ProductServiceTypes } from "../types/services"
@@ -178,7 +178,7 @@ export default class ProductService<TEntity extends Product = Product> {
async restore(
productIds: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
): Promise<[TEntity[], Record<string, unknown[]>]> {
return await this.productRepository_.restore(productIds, {
transactionManager: sharedContext.transactionManager,
})