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
+1 -1
View File
@@ -23,7 +23,7 @@
"ioredis": "^5.2.5",
"rimraf": "^5.0.1",
"typeorm": "^0.3.16",
"typescript": "^4.4.4",
"typescript": "^5.1.6",
"winston": "^3.8.2"
},
"scripts": {
+13 -2
View File
@@ -1,6 +1,6 @@
import { FindOptions } from "./index"
import { RepositoryTransformOptions } from "../common"
import { Context } from "../shared-context"
import { FindOptions } from "./index"
/**
* Data access layer (DAL) interface to implements for any repository service.
@@ -54,7 +54,10 @@ export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
context?: Context
): Promise<[T[], Record<string, unknown[]>]>
restore(ids: string[], context?: Context): Promise<T[]>
restore(
ids: string[],
context?: Context
): Promise<[T[], Record<string, unknown[]>]>
}
export interface TreeRepositoryService<T = any>
@@ -75,3 +78,11 @@ export interface TreeRepositoryService<T = any>
delete(id: string, context?: Context): Promise<void>
}
export type SoftDeleteReturn<TReturnableLinkableKeys = string> = {
returnLinkableKeys?: TReturnableLinkableKeys[]
}
export type RestoreReturn<TReturnableLinkableKeys = string> = {
returnLinkableKeys?: TReturnableLinkableKeys[]
}
+1
View File
@@ -9,6 +9,7 @@ export * from "./feature-flag"
export * from "./file-service"
export * from "./inventory"
export * from "./joiner"
export * from "./link-modules"
export * from "./logger"
export * from "./modules-sdk"
export * from "./pricing"
+3 -3
View File
@@ -13,11 +13,11 @@ import {
} from "./common"
import { FindConfig } from "../common"
import { JoinerServiceConfig } from "../joiner"
import { SharedContext } from ".."
import { ModuleJoinerConfig } from "../modules-sdk"
import { SharedContext } from "../shared-context"
export interface IInventoryService {
__joinerConfig(): JoinerServiceConfig
__joinerConfig(): ModuleJoinerConfig
listInventoryItems(
selector: FilterableInventoryItemProps,
config?: FindConfig<InventoryItemDTO>,
+8 -2
View File
@@ -28,7 +28,11 @@ export interface JoinerServiceConfig {
export interface JoinerArgument {
name: string
value?: any
field?: string
}
export interface JoinerDirective {
name: string
value?: any
}
export interface RemoteJoinerQuery {
@@ -38,10 +42,11 @@ export interface RemoteJoinerQuery {
property: string
fields: string[]
args?: JoinerArgument[]
relationships?: JoinerRelationship[]
directives?: { [field: string]: JoinerDirective[] }
}>
fields: string[]
args?: JoinerArgument[]
directives?: { [field: string]: JoinerDirective[] }
}
export interface RemoteNestedExpands {
@@ -54,6 +59,7 @@ export interface RemoteNestedExpands {
export interface RemoteExpandProperty {
property: string
parent: string
serviceConfig: JoinerServiceConfig
fields: string[]
args?: JoinerArgument[]
+49
View File
@@ -0,0 +1,49 @@
import { FindConfig } from "../common"
import { RestoreReturn, SoftDeleteReturn } from "../dal"
import { ModuleJoinerConfig } from "../modules-sdk"
import { Context } from "../shared-context"
export interface ILinkModule {
__joinerConfig(): ModuleJoinerConfig
list(
filters?: Record<string, unknown>,
config?: FindConfig<unknown>,
sharedContext?: Context
): Promise<unknown[]>
listAndCount(
filters?: Record<string, unknown>,
config?: FindConfig<unknown>,
sharedContext?: Context
): Promise<[unknown[], number]>
create(
primaryKeyOrBulkData:
| string
| string[]
| [string | string[], string, Record<string, unknown>?][],
foreignKeyData?: string,
sharedContext?: Context
): Promise<unknown[]>
dismiss(
primaryKeyOrBulkData: string | string[] | [string | string[], string][],
foreignKeyData?: string,
sharedContext?: Context
): Promise<unknown[]>
delete(data: unknown | unknown[], sharedContext?: Context): Promise<void>
softDelete(
data: unknown | unknown[],
config?: SoftDeleteReturn,
sharedContext?: Context
): Promise<Record<string, unknown[]> | void>
restore(
data: unknown | unknown[],
config?: RestoreReturn,
sharedContext?: Context
): Promise<Record<string, unknown[]> | void>
}
+89 -4
View File
@@ -1,7 +1,8 @@
import { JoinerServiceConfig } from "../joiner"
import { Logger } from "../logger"
import { JoinerRelationship, JoinerServiceConfig } from "../joiner"
import { MedusaContainer } from "../common"
import { RepositoryService } from "../dal"
import { Logger } from "../logger"
export type Constructor<T> = new (...args: any[]) => T
export * from "../common/medusa-container"
@@ -30,6 +31,9 @@ export type InternalModuleDeclaration = {
scope: MODULE_SCOPE.INTERNAL
resources: MODULE_RESOURCE_TYPE
dependencies?: string[]
/**
* @deprecated The property should not be used.
*/
resolve?: string
options?: Record<string, unknown>
alias?: string // If multiple modules are registered with the same key, the alias can be used to differentiate them
@@ -43,6 +47,7 @@ export type ExternalModuleDeclaration = {
url: string
keepAlive: boolean
}
options?: Record<string, unknown>
alias?: string // If multiple modules are registered with the same key, the alias can be used to differentiate them
main?: boolean // If the module is the main module for the key when multiple ones are registered
}
@@ -61,17 +66,38 @@ export type ModuleDefinition = {
registrationName: string
defaultPackage: string | false
label: string
/**
* @deprecated property will be removed in future versions
*/
canOverride?: boolean
/**
* @deprecated property will be removed in future versions
*/
isRequired?: boolean
isQueryable?: boolean // If the modules should be queryable via Remote Joiner
isQueryable?: boolean // If the module is queryable via Remote Joiner
dependencies?: string[]
defaultModuleDeclaration:
| InternalModuleDeclaration
| ExternalModuleDeclaration
}
export type LinkModuleDefinition = {
key: string
registrationName: string
label: string
dependencies?: string[]
defaultModuleDeclaration: InternalModuleDeclaration
}
type ModuleDeclaration = ExternalModuleDeclaration | InternalModuleDeclaration
export type ModuleConfig = ModuleDeclaration & {
module: string
path: string
definition: ModuleDefinition
}
export type LoadedModule = unknown & {
__joinerConfig: JoinerServiceConfig
__joinerConfig: ModuleJoinerConfig
__definition: ModuleDefinition
}
@@ -91,6 +117,60 @@ export type ModulesResponse = {
resolution: string | false
}[]
export type ModuleJoinerConfig = Omit<
JoinerServiceConfig,
"serviceName" | "primaryKeys" | "relationships" | "extends"
> & {
relationships?: ModuleJoinerRelationship[]
extends?: {
serviceName: string
relationship: ModuleJoinerRelationship
}[]
serviceName?: string
primaryKeys?: string[]
isLink?: boolean // If the module is a link module
linkableKeys?: string[] // Keys that can be used to link to other modules
isReadOnlyLink?: boolean // If true it expands a RemoteQuery property but doesn't create a pivot table
databaseConfig?: {
tableName?: string // Name of the pivot table. If not provided it is auto generated
idPrefix?: string // Prefix for the id column. If not provided it is "link"
extraFields?: Record<
string,
{
type:
| "date"
| "time"
| "datetime"
| "bigint"
| "blob"
| "uint8array"
| "array"
| "enumArray"
| "enum"
| "json"
| "integer"
| "smallint"
| "tinyint"
| "mediumint"
| "float"
| "double"
| "boolean"
| "decimal"
| "string"
| "uuid"
| "text"
defaultValue?: string
nullable?: boolean
options?: Record<string, unknown> // Mikro-orm options for the column
}
>
}
}
export declare type ModuleJoinerRelationship = JoinerRelationship & {
deleteCascade?: boolean // If true, the link joiner will cascade deleting the relationship
}
export type ModuleExports = {
service: Constructor<any>
loaders?: ModuleLoaderFunction[]
@@ -114,6 +194,11 @@ export interface ModuleServiceInitializeOptions {
connection?: any
clientUrl?: string
schema?: string
host?: string
port?: number
user?: string
password?: string
database?: string
driverOptions?: Record<string, unknown>
debug?: boolean
}
+10 -5
View File
@@ -27,12 +27,13 @@ import {
UpdateProductTypeDTO,
} from "./common"
import { Context } from "../shared-context"
import { FindConfig } from "../common"
import { JoinerServiceConfig } from "../joiner"
import { RestoreReturn, SoftDeleteReturn } from "../dal"
import { ModuleJoinerConfig } from "../modules-sdk"
import { Context } from "../shared-context"
export interface IProductModuleService {
__joinerConfig(): JoinerServiceConfig
__joinerConfig(): ModuleJoinerConfig
retrieve(
productId: string,
@@ -241,9 +242,13 @@ export interface IProductModuleService {
softDelete<TReturnableLinkableKeys extends string = string>(
productIds: string[],
config?: { returnLinkableKeys?: TReturnableLinkableKeys[] },
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
sharedContext?: Context
): Promise<Record<string, string[]> | void>
restore(productIds: string[], sharedContext?: Context): Promise<ProductDTO[]>
restore<TReturnableLinkableKeys extends string = string>(
productIds: string[],
config?: RestoreReturn<TReturnableLinkableKeys>,
sharedContext?: Context
): Promise<Record<string, string[]> | void>
}
+5 -4
View File
@@ -1,6 +1,3 @@
import { FindConfig } from "../common/common"
import { JoinerServiceConfig } from "../joiner"
import { SharedContext } from "../shared-context"
import {
CreateStockLocationInput,
FilterableStockLocationProps,
@@ -8,8 +5,12 @@ import {
UpdateStockLocationInput,
} from "./common"
import { FindConfig } from "../common/common"
import { ModuleJoinerConfig } from "../modules-sdk"
import { SharedContext } from "../shared-context"
export interface IStockLocationService {
__joinerConfig(): JoinerServiceConfig
__joinerConfig(): ModuleJoinerConfig
list(
selector: FilterableStockLocationProps,
config?: FindConfig<StockLocationDTO>,