feat: Product Module (#4161)
* chore: boilerplate setup * wip: add Product, ProductTag, ProductType, ProductCollection models * wip: `IProductService` definition * wip: test function in index, build passing * fix: where condition * chore: get boilerplate working with modules sdk, create a boilerplate test, create product variant model, register services properly * chore: added variant to model * chore: changed definition details + add migrator * cleanup and update product entity * Update product unique index to include soft deleted * Migrations tests * generated migration * update dev orm config * add path aliases * WIP * chore: added boilerplate integration test + database helper + product variant migraiton + model * chore: remove old test utils * update ts and jest config to include path aliases * tweak config * WIP migrations variant * Migrations round * integration tests migrations polishing * chore: fixed issues with test db * fix path aliases when published * use ts-alias * fix connection loader * fixes * wip: product list * (WIP): Data access layer * (WIP): DAL cleanup services * wip: `ProductTag` DAL * wip: `ProductTag` expose list in product service * (WIP): Continue DAL and test list product filtering/populate * WIP: unit tests * chore: added tests for service - productvariant * chore: WIP finding issues with orm manager fork * WIP fix fields selection * chore: make text fixes work * (WIP) product integration * (WIP) product integration * chore: create a product in variant test * list product with relations * wip: `ProductTag` service + integrations * chore: added with and without serialization example * chore: remove only in spec * wip: `ProductCollection` service + integrations * uncomment product.variants * Update type IProductService * (WIP) type work * (WIP) Product variants relation * WIP: replacable data layer * (WIP): Use bundle types * WIP: update type * WIP upadte tests * WIP * wip: options/option values entites * (WIP): cleanup * wip: add option value to variant, fix collection * Integration tests for custom data access layer * update tests * chore: merge with latest branch * chore: scope tests to relations and add category to models/index * chore: ignore dist folders for jest * chore: modularize spec data file * improve DX * module fixture naming * chore: added category tests + fix model * chore: use kebab case * chore: allow scoping products by category id * chore: replace `kebabCase` import * feat: add `deleted_at` to options * improve typings * chore: wip * fix query util * revert webpack * fix: update option models, create option DTOs, tests wip, fix `deduplicateIfNecessary` returning `undefined` * fix: merge conflict * WIP connection * rm unsues deps * fix migrations * fix query util * chore: adds mpath on creation * WIP update types * improve typings * WIP typeings improvement * WIP * deps * chore: package medusa/product ot medusa-commerce/product * chore: added product categories service + descendants filter * add missing index * Add support for strict categories not in * Add support for strict categories not in * lint * rename module * rename module * Create small-ducks-doubt.md * yarn lock * update initialise * chore: fix/finalise DTOs * fix: wrong types in `IProductService` * fix type * Load database config from env if present (#4175) * Load database config from env if present * Load database config from env if present * options optionnal * update util * add defaults * improve filterable interfaces * fix import * fix types * remove medusa-telemetry from modules-sdk * WIP fixing webpack issues when bootstraping module * cleanup * improve loading driver options * cleanup * yarn lock * fix import * improve sdk types and naming * align orther modules initialise method * fix module tests with singleton module * fix module tests with singleton module * add up/down migration scripts * update types * scripts * cleanup migrations and scripts * hash module singleton * cleanup migration * cleanup * fix stringifyCircular usage * improvements * fix deps * fix deps * improve load config utils * improve load config utils * fix deps * add declaration to the build * update yarn * Do not resolve a module path if the exports are explicitly given * fix module registration resolution path when exports are provided. Explicitly check for false and assign an empty string in this scenario for segregation purpose * add comment * fix migration options to prevent set replica errors * chore: update types to a proper depedency * chore: update type package * add seed scripts * Add descriptive error during database config loading * use MedusaError * chore: added lodash to package * add more test to the database config loader util * create bin scripts * add bin * update argv retrieval * update package.json * chore: add product category to injected deps * chore: replace with product category service * move dotenv usage to the functions * do not load db if there is custom manager * chore: fix some tests on products repo * chore: fixed product spec * chore: skip products module on modules register * stringifyCircular update * chore: fix incorrect module resolution * fix: circular stringify and non required module loading * yarn lock * target es5 * chore: mikro-orm back to 5.7.4 * revert module registry * skip external modules * es2020 * update indexes, migration and integration tests * rm only * unit test script should only run unit tests * Exclude product integration from the unit tests and make use of the global integration script to run all packages integration tests * fix integration tests * improve setup * cleanup * log error on setup fail * Create enum like for package names * chore: remove EOL * chore: review part 2 * renamve gateway to productModuleService * chore: added filters and collections to productmoduleservice * chore: add collection to the singleton instance * chore: remove skipped test + add todo * fix indexes on fields and relations + update migration * update yarn lock * update idx * add foreign key * rename interface and add listCategories * rename product module definition --------- Co-authored-by: fPolic <frane@medusajs.com> Co-authored-by: Riqwan Thamir <rmthamir@gmail.com> Co-authored-by: Carlos R. L. Rodrigues <rodrigolr@gmail.com>
This commit is contained in:
co-authored by
fPolic
Riqwan Thamir
Carlos R. L. Rodrigues
parent
ffeeb84d97
commit
14c0f62f84
@@ -2,7 +2,9 @@ export * as CacheTypes from "./cache"
|
||||
export * as CommonTypes from "./common"
|
||||
export * as EventBusTypes from "./event-bus"
|
||||
export * as InventoryTypes from "./inventory"
|
||||
export * as ProductTypes from "./product"
|
||||
export * as ModulesSdkTypes from "./modules-sdk"
|
||||
export * as SearchTypes from "./search"
|
||||
export * as StockLocationTypes from "./stock-location"
|
||||
export * as TransactionBaseTypes from "./transaction-base"
|
||||
export * as DAL from "./dal"
|
||||
|
||||
@@ -126,6 +126,8 @@ export type DeleteResponse = {
|
||||
}
|
||||
|
||||
export interface EmptyQueryParams {}
|
||||
// TODO: Build a tree repository options from this
|
||||
export interface RepositoryTransformOptions {}
|
||||
|
||||
export interface DateComparisonOperator {
|
||||
lt?: Date
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Dictionary, FilterQuery, Order } from "./utils"
|
||||
|
||||
export { FilterQuery } from "./utils"
|
||||
export interface BaseFilterable<T> {
|
||||
$and?: T
|
||||
$or?: T
|
||||
}
|
||||
|
||||
export interface OptionsQuery<T, P extends string = never> {
|
||||
populate?: string[]
|
||||
orderBy?: Order<T> | Order<T>[]
|
||||
limit?: number
|
||||
offset?: number
|
||||
fields?: string[]
|
||||
groupBy?: string | string[]
|
||||
filters?: Dictionary<boolean | Dictionary> | string[] | boolean
|
||||
}
|
||||
|
||||
export type FindOptions<T = any> = {
|
||||
where: FilterQuery<T>
|
||||
options?: OptionsQuery<T, any>
|
||||
}
|
||||
|
||||
export * from "./repository-service"
|
||||
@@ -0,0 +1,12 @@
|
||||
import { FindOptions } from "./index"
|
||||
import { RepositoryTransformOptions } from "../common"
|
||||
|
||||
/**
|
||||
* Data access layer (DAL) interface to implements for any repository service.
|
||||
* This layer helps to separate the business logic (service layer) from accessing the
|
||||
* ORM directly and allows to switch to another ORM without changing the business logic.
|
||||
*/
|
||||
export interface RepositoryService<T = any> {
|
||||
find(options?: FindOptions<T>, transformOptions?: RepositoryTransformOptions): Promise<T[]>
|
||||
findAndCount(options?: FindOptions<T>, transformOptions?: RepositoryTransformOptions): Promise<[T[], number]>
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
type ExpandProperty<T> = T extends (infer U)[] ? NonNullable<U> : NonNullable<T>
|
||||
|
||||
export type Dictionary<T = any> = {
|
||||
[k: string]: T
|
||||
}
|
||||
|
||||
type Query<T> = T extends object
|
||||
? T extends Scalar
|
||||
? never
|
||||
: FilterQuery<T>
|
||||
: FilterValue<T>
|
||||
|
||||
type ExpandScalar<T> =
|
||||
| null
|
||||
| (T extends string ? string | RegExp : T extends Date ? Date | string : T)
|
||||
type Scalar =
|
||||
| boolean
|
||||
| number
|
||||
| string
|
||||
| bigint
|
||||
| symbol
|
||||
| Date
|
||||
| RegExp
|
||||
| Buffer
|
||||
| {
|
||||
toHexString(): string
|
||||
}
|
||||
|
||||
type ExcludeFunctions<T, K extends keyof T> = T[K] extends Function
|
||||
? never
|
||||
: K extends symbol
|
||||
? never
|
||||
: K
|
||||
|
||||
type ReadonlyPrimary<T> = T extends any[] ? Readonly<T> : T
|
||||
|
||||
declare const PrimaryKeyType: unique symbol
|
||||
type Primary<T> = T extends {
|
||||
[PrimaryKeyType]?: infer PK
|
||||
}
|
||||
? ReadonlyPrimary<PK>
|
||||
: T extends {
|
||||
_id?: infer PK
|
||||
}
|
||||
? ReadonlyPrimary<PK> | string
|
||||
: T extends {
|
||||
uuid?: infer PK
|
||||
}
|
||||
? ReadonlyPrimary<PK>
|
||||
: T extends {
|
||||
id?: infer PK
|
||||
}
|
||||
? ReadonlyPrimary<PK>
|
||||
: never
|
||||
|
||||
export type OperatorMap<T> = {
|
||||
$and?: Query<T>[]
|
||||
$or?: Query<T>[]
|
||||
$eq?: ExpandScalar<T> | ExpandScalar<T>[]
|
||||
$ne?: ExpandScalar<T>
|
||||
$in?: ExpandScalar<T>[]
|
||||
$nin?: ExpandScalar<T>[]
|
||||
$not?: Query<T>
|
||||
$gt?: ExpandScalar<T>
|
||||
$gte?: ExpandScalar<T>
|
||||
$lt?: ExpandScalar<T>
|
||||
$lte?: ExpandScalar<T>
|
||||
$like?: string
|
||||
$re?: string
|
||||
$ilike?: string
|
||||
$fulltext?: string
|
||||
$overlap?: string[]
|
||||
$contains?: string[]
|
||||
$contained?: string[]
|
||||
$exists?: boolean
|
||||
}
|
||||
|
||||
type FilterValue2<T> = T | ExpandScalar<T> | Primary<T>
|
||||
type FilterValue<T> =
|
||||
| OperatorMap<FilterValue2<T>>
|
||||
| FilterValue2<T>
|
||||
| FilterValue2<T>[]
|
||||
| null
|
||||
|
||||
type PrevLimit = [never, 1, 2, 3]
|
||||
|
||||
export type FilterQuery<T = any, Prev extends number = 3> = Prev extends never
|
||||
? never
|
||||
: {
|
||||
[Key in keyof T]?: T[Key] extends
|
||||
| boolean
|
||||
| number
|
||||
| string
|
||||
| bigint
|
||||
| symbol
|
||||
| Date
|
||||
? T[Key] | OperatorMap<T[Key]>
|
||||
: T[Key] extends infer U
|
||||
? U extends { [x: number]: infer V }
|
||||
? V extends object
|
||||
? FilterQuery<Partial<V>, PrevLimit[Prev]>
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
}
|
||||
|
||||
declare enum QueryOrder {
|
||||
ASC = "ASC",
|
||||
DESC = "DESC",
|
||||
asc = "asc",
|
||||
desc = "desc",
|
||||
}
|
||||
|
||||
type QueryOrderKeysFlat = QueryOrder | 1 | -1 | keyof typeof QueryOrder
|
||||
type QueryOrderKeys<T> = QueryOrderKeysFlat | QueryOrderMap<T>
|
||||
type QueryOrderMap<T> = {
|
||||
[K in keyof T as ExcludeFunctions<T, K>]?: QueryOrderKeys<
|
||||
ExpandProperty<T[K]>
|
||||
>
|
||||
}
|
||||
|
||||
export type Order<T> = {
|
||||
[key in keyof T]?:
|
||||
| "ASC"
|
||||
| "DESC"
|
||||
| Order<T[key] extends Array<any> ? T[key][0] : T[key]>
|
||||
}
|
||||
@@ -4,7 +4,10 @@ export * from "./common"
|
||||
export * from "./event-bus"
|
||||
export * from "./inventory"
|
||||
export * from "./modules-sdk"
|
||||
export * from "./product"
|
||||
export * from "./product-category"
|
||||
export * from "./search"
|
||||
export * from "./shared-context"
|
||||
export * from "./stock-location"
|
||||
export * from "./transaction-base"
|
||||
export * from "./dal"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Logger as _Logger } from "winston"
|
||||
import { MedusaContainer } from "../common/medusa-container"
|
||||
|
||||
export type Constructor<T> = new (...args: any[]) => T
|
||||
export * from "../common/medusa-container"
|
||||
|
||||
@@ -39,7 +40,7 @@ export type InternalModuleDeclaration = {
|
||||
|
||||
export type ExternalModuleDeclaration = {
|
||||
scope: MODULE_SCOPE.EXTERNAL
|
||||
server: {
|
||||
server?: {
|
||||
type: "http"
|
||||
url: string
|
||||
keepAlive: boolean
|
||||
@@ -52,6 +53,7 @@ export type ModuleResolution = {
|
||||
options?: Record<string, unknown>
|
||||
dependencies?: string[]
|
||||
moduleDeclaration?: InternalModuleDeclaration | ExternalModuleDeclaration
|
||||
moduleExports?: ModuleExports
|
||||
}
|
||||
|
||||
export type ModuleDefinition = {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./repository"
|
||||
@@ -0,0 +1,5 @@
|
||||
import { RepositoryTransformOptions } from '../common'
|
||||
|
||||
export interface ProductCategoryTransformOptions extends RepositoryTransformOptions {
|
||||
includeDescendantsTree?: boolean
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import { BaseFilterable } from "../dal"
|
||||
import { OperatorMap } from "../dal/utils"
|
||||
|
||||
export enum ProductStatus {
|
||||
DRAFT = "draft",
|
||||
PROPOSED = "proposed",
|
||||
PUBLISHED = "published",
|
||||
REJECTED = "rejected",
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO in and out of the module (module API)
|
||||
*/
|
||||
|
||||
export interface ProductDTO {
|
||||
id: string
|
||||
title: string
|
||||
handle?: string | null
|
||||
subtitle?: string | null
|
||||
description?: string | null
|
||||
is_giftcard: boolean
|
||||
status: ProductStatus
|
||||
thumbnail?: string | null
|
||||
weight?: number | null
|
||||
length?: number | null
|
||||
height?: number | null
|
||||
origin_country?: string | null
|
||||
hs_code?: string | null
|
||||
mid_code?: string | null
|
||||
material?: string | null
|
||||
collection: ProductCollectionDTO
|
||||
categories?: ProductCategoryDTO[] | null
|
||||
type: ProductTypeDTO[]
|
||||
tags: ProductTagDTO[]
|
||||
variants: ProductVariantDTO[]
|
||||
options: ProductOptionDTO[]
|
||||
discountable?: boolean
|
||||
external_id?: string | null
|
||||
created_at?: string | Date
|
||||
updated_at?: string | Date
|
||||
deleted_at?: string | Date
|
||||
}
|
||||
|
||||
export interface ProductVariantDTO {
|
||||
id: string
|
||||
title: string
|
||||
sku?: string | null
|
||||
barcode?: string | null
|
||||
ean?: string | null
|
||||
upc?: string | null
|
||||
inventory_quantity: number
|
||||
allow_backorder?: boolean
|
||||
manage_inventory?: boolean
|
||||
hs_code?: string | null
|
||||
origin_country?: string | null
|
||||
mid_code?: string | null
|
||||
material?: string | null
|
||||
weight?: number | null
|
||||
length?: number | null
|
||||
height?: number | null
|
||||
width?: number | null
|
||||
options: ProductOptionValueDTO
|
||||
metadata?: Record<string, unknown> | null
|
||||
product: ProductDTO
|
||||
variant_rank?: number | null
|
||||
created_at: string | Date
|
||||
updated_at: string | Date
|
||||
deleted_at: string | Date
|
||||
}
|
||||
|
||||
export interface ProductCategoryDTO {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
handle?: string
|
||||
is_active?: boolean
|
||||
is_internal?: boolean
|
||||
rank?: number
|
||||
parent_category?: ProductCategoryDTO
|
||||
category_children: ProductCategoryDTO[]
|
||||
created_at: string | Date
|
||||
updated_at: string | Date
|
||||
}
|
||||
|
||||
export interface ProductTagDTO {
|
||||
id: string
|
||||
value: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
products: ProductDTO[]
|
||||
}
|
||||
|
||||
export interface ProductCollectionDTO {
|
||||
id: string
|
||||
title: string
|
||||
handle: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
deleted_at?: string | Date
|
||||
}
|
||||
|
||||
export interface ProductTypeDTO {
|
||||
id: string
|
||||
value: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
deleted_at?: string | Date
|
||||
}
|
||||
|
||||
export interface ProductOptionDTO {
|
||||
id: string
|
||||
title: string
|
||||
product: ProductDTO
|
||||
values: ProductOptionValueDTO[]
|
||||
metadata?: Record<string, unknown> | null
|
||||
deleted_at?: string | Date
|
||||
}
|
||||
|
||||
export interface ProductOptionValueDTO {
|
||||
id: string
|
||||
value: string
|
||||
option: ProductOptionDTO
|
||||
variant: ProductVariantDTO
|
||||
metadata?: Record<string, unknown> | null
|
||||
deleted_at?: string | Date
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters/Config (module API input filters and config)
|
||||
*/
|
||||
export interface FilterableProductProps
|
||||
extends BaseFilterable<FilterableProductProps> {
|
||||
handle?: string | string[]
|
||||
id?: string | string[]
|
||||
tags?: { value?: string[] }
|
||||
categories?: {
|
||||
id?: string | string[] | OperatorMap<string>
|
||||
}
|
||||
category_ids?: string | string[] | OperatorMap<string>
|
||||
}
|
||||
|
||||
export interface FilterableProductTagProps
|
||||
extends BaseFilterable<FilterableProductTagProps> {
|
||||
id?: string | string[]
|
||||
value?: string
|
||||
}
|
||||
|
||||
export interface FilterableProductCollectionProps
|
||||
extends BaseFilterable<FilterableProductCollectionProps> {
|
||||
id?: string | string[]
|
||||
title?: string
|
||||
}
|
||||
|
||||
export interface FilterableProductVariantProps
|
||||
extends BaseFilterable<FilterableProductVariantProps> {
|
||||
id?: string | string[]
|
||||
sku?: string | string[]
|
||||
options?: { id?: string[] }
|
||||
}
|
||||
|
||||
export interface FilterableProductCategoryProps
|
||||
extends BaseFilterable<FilterableProductCategoryProps> {
|
||||
id?: string | string[]
|
||||
parent_category_id?: string | string[] | null
|
||||
handle?: string | string[]
|
||||
is_active?: boolean
|
||||
is_internal?: boolean
|
||||
include_descendants_tree?: boolean
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./service"
|
||||
export * from "./common"
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
FilterableProductCategoryProps,
|
||||
FilterableProductCollectionProps,
|
||||
FilterableProductProps,
|
||||
FilterableProductTagProps,
|
||||
FilterableProductVariantProps,
|
||||
ProductCategoryDTO,
|
||||
ProductCollectionDTO,
|
||||
ProductDTO,
|
||||
ProductTagDTO,
|
||||
ProductVariantDTO,
|
||||
} from "./common"
|
||||
import { FindConfig } from "../common"
|
||||
import { SharedContext } from "../shared-context"
|
||||
|
||||
export interface IProductModuleService<
|
||||
TProduct = any,
|
||||
TProductVariant = any,
|
||||
TProductTag = any,
|
||||
TProductCollection = any,
|
||||
TProductCategory = any
|
||||
> {
|
||||
list(
|
||||
filter: FilterableProductProps,
|
||||
config?: FindConfig<ProductDTO>,
|
||||
context?: SharedContext
|
||||
): Promise<ProductDTO[]>
|
||||
|
||||
listAndCount(
|
||||
filter: FilterableProductProps,
|
||||
config?: FindConfig<ProductDTO>,
|
||||
context?: SharedContext
|
||||
): Promise<[ProductDTO[], number]>
|
||||
|
||||
listTags(
|
||||
filter: FilterableProductTagProps,
|
||||
config?: FindConfig<ProductTagDTO>,
|
||||
context?: SharedContext
|
||||
): Promise<ProductTagDTO[]>
|
||||
|
||||
listVariants(
|
||||
filter: FilterableProductVariantProps,
|
||||
config?: FindConfig<ProductVariantDTO>,
|
||||
context?: SharedContext
|
||||
): Promise<ProductVariantDTO[]>
|
||||
|
||||
listCollections(
|
||||
filter: FilterableProductCollectionProps,
|
||||
config?: FindConfig<ProductCollectionDTO>,
|
||||
context?: SharedContext
|
||||
): Promise<ProductCollectionDTO[]>
|
||||
|
||||
listCategories(
|
||||
filters: FilterableProductCategoryProps,
|
||||
config?: FindConfig<ProductCategoryDTO>,
|
||||
sharedContext?: SharedContext
|
||||
): Promise<ProductCategoryDTO[]>
|
||||
}
|
||||
Reference in New Issue
Block a user