From e7e715ac177bd0b883ffa0d72a208580012bfbab Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 18 Apr 2022 15:45:33 +0200 Subject: [PATCH] feat(medusa): Split base service to its related TransactionBaseService and utilities methods when required --- .../interfaces/__tests__/base-service.spec.ts | 37 +--- .../medusa/src/interfaces/base-service.ts | 202 ------------------ packages/medusa/src/interfaces/index.ts | 2 +- .../src/utils/__tests__/build-query.spec.ts | 28 +++ packages/medusa/src/utils/build-query.ts | 112 ++++++++++ packages/medusa/src/utils/set-metadata.ts | 29 +++ packages/medusa/src/utils/validate-id.ts | 41 ++++ 7 files changed, 218 insertions(+), 233 deletions(-) delete mode 100644 packages/medusa/src/interfaces/base-service.ts create mode 100644 packages/medusa/src/utils/__tests__/build-query.spec.ts create mode 100644 packages/medusa/src/utils/build-query.ts create mode 100644 packages/medusa/src/utils/set-metadata.ts create mode 100644 packages/medusa/src/utils/validate-id.ts diff --git a/packages/medusa/src/interfaces/__tests__/base-service.spec.ts b/packages/medusa/src/interfaces/__tests__/base-service.spec.ts index 9fa804e4f1..65c5b62608 100644 --- a/packages/medusa/src/interfaces/__tests__/base-service.spec.ts +++ b/packages/medusa/src/interfaces/__tests__/base-service.spec.ts @@ -1,10 +1,13 @@ -import { BaseService } from "../base-service" -import { In, Not } from "typeorm" +import { EntityManager } from "typeorm" import { MockManager } from "medusa-test-utils" +import { TransactionBaseService } from "../transaction-base-service" -describe("BaseService", () => { +describe("TransactionBaseService", () => { it("should cloned the child class withTransaction", () => { - class Child extends BaseService { + class Child extends TransactionBaseService { + protected manager_!: EntityManager + protected transactionManager_!: EntityManager + constructor(protected readonly container) { super(container); this.container = container @@ -32,30 +35,4 @@ describe("BaseService", () => { expect(child2.getTransactionManager()).toBeTruthy() expect((child2.getTransactionManager() as any)?.testProp).toBe('testProp') }) - - describe("buildQuery_", () => { - const baseService = new BaseService({}, {}) - - it("successfully creates query", () => { - const q = baseService.buildQuery_( - { - id: "1234", - test1: ["123", "12", "1"], - test2: Not("this"), - }, - { - relations: ["1234"], - } - ) - - expect(q).toEqual({ - where: { - id: "1234", - test1: In(["123", "12", "1"]), - test2: Not("this"), - }, - relations: ["1234"], - }) - }) - }) }) \ No newline at end of file diff --git a/packages/medusa/src/interfaces/base-service.ts b/packages/medusa/src/interfaces/base-service.ts deleted file mode 100644 index b87c15dd32..0000000000 --- a/packages/medusa/src/interfaces/base-service.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { MedusaError } from "medusa-core-utils" -import { EntityManager, FindOperator, In, Raw } from "typeorm" -import { FindConfig, Writable } from "../types/common" - -type Selector = { [key in keyof TEntity]?: unknown } - -/** - * Common functionality for Services - * @interface - */ -export class BaseService< - TChild extends BaseService, - TContainer = unknown -> { - protected transactionManager_: EntityManager | undefined - protected manager_: EntityManager - private readonly container_: TContainer - - constructor( - container: TContainer, - protected readonly configModule?: Record - ) { - this.container_ = container - } - - /** - * Used to build TypeORM queries. - * @param selector The selector - * @param config The config - * @return The QueryBuilderConfig - */ - buildQuery_( - selector: Selector, - config: FindConfig = {} - ): FindConfig & { - where: Partial> - withDeleted?: boolean - } { - const build = ( - obj: Record - ): Partial> => { - return Object.entries(obj).reduce((acc, [key, value]: any) => { - // Undefined values indicate that they have no significance to the query. - // If the query is looking for rows where a column is not set it should use null instead of undefined - if (typeof value === "undefined") { - return acc - } - - const subquery: { - operator: "<" | ">" | "<=" | ">=" - value: unknown - }[] = [] - - switch (true) { - case value instanceof FindOperator: - acc[key] = value - break - case Array.isArray(value): - acc[key] = In([...(value as unknown[])]) - break - case value !== null && typeof value === "object": - Object.entries(value).map(([modifier, val]) => { - switch (modifier) { - case "lt": - subquery.push({ operator: "<", value: val }) - break - case "gt": - subquery.push({ operator: ">", value: val }) - break - case "lte": - subquery.push({ operator: "<=", value: val }) - break - case "gte": - subquery.push({ operator: ">=", value: val }) - break - default: - acc[key] = value - break - } - }) - - if (subquery.length) { - acc[key] = Raw( - (a) => - subquery - .map((s, index) => `${a} ${s.operator} :${index}`) - .join(" AND "), - subquery.map((s) => s.value) - ) - } - break - default: - acc[key] = value - break - } - - return acc - }, {} as Partial>) - } - - const query: FindConfig & { - where: Partial> - withDeleted?: boolean - } = { - where: build(selector), - } - - if ("deleted_at" in selector) { - query.withDeleted = true - } - - if ("skip" in config) { - query.skip = config.skip - } - - if ("take" in config) { - query.take = config.take - } - - if ("relations" in config) { - query.relations = config.relations - } - - if ("select" in config) { - query.select = config.select - } - - if ("order" in config) { - query.order = config.order - } - - return query - } - - /** - * Confirms whether a given raw id is valid. Fails if the provided - * id is null or undefined. The validate function takes an optional config - * param, to support checking id prefix and length. - * @param rawId - the id to validate. - * @param config - optional config - * @returns the rawId given that nothing failed - */ - validateId_( - rawId: string, - config: { prefix?: string; length?: number } = {} - ): string { - const { prefix, length } = config - if (!rawId) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Failed to validate id: ${rawId}` - ) - } - - if (prefix || length) { - const [pre, rand] = rawId.split("_") - if (prefix && pre !== prefix) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `The provided id: ${rawId} does not adhere to prefix constraint: ${prefix}` - ) - } - - if (length && length !== rand.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `The provided id: ${rawId} does not adhere to length constraint: ${length}` - ) - } - } - - return rawId - } - - /** - * Dedicated method to set metadata. - * @param obj - the entity to apply metadata to. - * @param metadata - the metadata to set - * @return resolves to the updated result. - */ - setMetadata_( - obj: { metadata: Record }, - metadata: Record - ): Record { - const existing = obj.metadata || {} - const newData = {} - for (const [key, value] of Object.entries(metadata)) { - if (typeof key !== "string") { - throw new MedusaError( - MedusaError.Types.INVALID_ARGUMENT, - "Key type is invalid. Metadata keys must be strings" - ) - } - newData[key] = value - } - - return { - ...existing, - ...newData, - } - } -} diff --git a/packages/medusa/src/interfaces/index.ts b/packages/medusa/src/interfaces/index.ts index 7552d3c149..9472f26f30 100644 --- a/packages/medusa/src/interfaces/index.ts +++ b/packages/medusa/src/interfaces/index.ts @@ -1,4 +1,4 @@ export * from "./tax-calculation-strategy" export * from "./cart-completion-strategy" export * from "./tax-service" -export * from "./base-service" +export * from "./transaction-base-service" diff --git a/packages/medusa/src/utils/__tests__/build-query.spec.ts b/packages/medusa/src/utils/__tests__/build-query.spec.ts new file mode 100644 index 0000000000..f9a4819390 --- /dev/null +++ b/packages/medusa/src/utils/__tests__/build-query.spec.ts @@ -0,0 +1,28 @@ +import { In, Not } from "typeorm" +import { buildQuery } from "../build-query" + +describe('buildQuery', () => { + describe("buildQuery_", () => { + it("successfully creates query", () => { + const q = buildQuery( + { + id: "1234", + test1: ["123", "12", "1"], + test2: Not("this"), + }, + { + relations: ["1234"], + } + ) + + expect(q).toEqual({ + where: { + id: "1234", + test1: In(["123", "12", "1"]), + test2: Not("this"), + }, + relations: ["1234"], + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/medusa/src/utils/build-query.ts b/packages/medusa/src/utils/build-query.ts new file mode 100644 index 0000000000..103af54379 --- /dev/null +++ b/packages/medusa/src/utils/build-query.ts @@ -0,0 +1,112 @@ +import { FindConfig, Writable } from "../types/common" +import { FindOperator, In, Raw } from "typeorm" + +type Selector = { [key in keyof TEntity]?: unknown } +/** +* Used to build TypeORM queries. +* @param selector The selector +* @param config The config +* @return The QueryBuilderConfig +*/ +export function buildQuery( + selector: Selector, + config: FindConfig = {} +): FindConfig & { + where: Partial> + withDeleted?: boolean +} { + const build = ( + obj: Record + ): Partial> => { + return Object.entries(obj).reduce((acc, [key, value]: any) => { + // Undefined values indicate that they have no significance to the query. + // If the query is looking for rows where a column is not set it should use null instead of undefined + if (typeof value === "undefined") { + return acc + } + + const subquery: { + operator: "<" | ">" | "<=" | ">=" + value: unknown + }[] = [] + + switch (true) { + case value instanceof FindOperator: + acc[key] = value + break + case Array.isArray(value): + acc[key] = In([...(value as unknown[])]) + break + case value !== null && typeof value === "object": + Object.entries(value).map(([modifier, val]) => { + switch (modifier) { + case "lt": + subquery.push({ operator: "<", value: val }) + break + case "gt": + subquery.push({ operator: ">", value: val }) + break + case "lte": + subquery.push({ operator: "<=", value: val }) + break + case "gte": + subquery.push({ operator: ">=", value: val }) + break + default: + acc[key] = value + break + } + }) + + if (subquery.length) { + acc[key] = Raw( + (a) => + subquery + .map((s, index) => `${a} ${s.operator} :${index}`) + .join(" AND "), + subquery.map((s) => s.value) + ) + } + break + default: + acc[key] = value + break + } + + return acc + }, {} as Partial>) + } + + const query: FindConfig & { + where: Partial> + withDeleted?: boolean + } = { + where: build(selector), + } + + if ("deleted_at" in selector) { + query.withDeleted = true + } + + if ("skip" in config) { + query.skip = config.skip + } + + if ("take" in config) { + query.take = config.take + } + + if ("relations" in config) { + query.relations = config.relations + } + + if ("select" in config) { + query.select = config.select + } + + if ("order" in config) { + query.order = config.order + } + + return query +} \ No newline at end of file diff --git a/packages/medusa/src/utils/set-metadata.ts b/packages/medusa/src/utils/set-metadata.ts new file mode 100644 index 0000000000..38399e1dbb --- /dev/null +++ b/packages/medusa/src/utils/set-metadata.ts @@ -0,0 +1,29 @@ +import { MedusaError } from "medusa-core-utils/dist" + +/** +* Dedicated method to set metadata. +* @param obj - the entity to apply metadata to. +* @param metadata - the metadata to set +* @return resolves to the updated result. +*/ +export function setMetadata_( + obj: { metadata: Record }, + metadata: Record +): Record { + const existing = obj.metadata || {} + const newData = {} + for (const [key, value] of Object.entries(metadata)) { + if (typeof key !== "string") { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Key type is invalid. Metadata keys must be strings" + ) + } + newData[key] = value + } + + return { + ...existing, + ...newData, + } +} \ No newline at end of file diff --git a/packages/medusa/src/utils/validate-id.ts b/packages/medusa/src/utils/validate-id.ts new file mode 100644 index 0000000000..2c616fad48 --- /dev/null +++ b/packages/medusa/src/utils/validate-id.ts @@ -0,0 +1,41 @@ +/** +* Confirms whether a given raw id is valid. Fails if the provided +* id is null or undefined. The validate function takes an optional config +* param, to support checking id prefix and length. +* @param rawId - the id to validate. +* @param config - optional config +* @returns the rawId given that nothing failed +*/ +import { MedusaError } from "medusa-core-utils/dist" + +export function validateId_( + rawId: string, + config: { prefix?: string; length?: number } = {} +): string { + const { prefix, length } = config + if (!rawId) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Failed to validate id: ${rawId}` + ) + } + + if (prefix || length) { + const [pre, rand] = rawId.split("_") + if (prefix && pre !== prefix) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `The provided id: ${rawId} does not adhere to prefix constraint: ${prefix}` + ) + } + + if (length && length !== rand.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `The provided id: ${rawId} does not adhere to length constraint: ${length}` + ) + } + } + + return rawId +} \ No newline at end of file