feat(medusa): Split base service to its related TransactionBaseService and utilities methods when required
This commit is contained in:
1 parent
99146b7403
commit
e7e715ac17
7 files changed
+218
-233
No files matched your search
@@ -1,10 +1,13 @@
|
|||||||
import { BaseService } from "../base-service"
|
import { EntityManager } from "typeorm"
|
||||||
import { In, Not } from "typeorm"
|
|
||||||
import { MockManager } from "medusa-test-utils"
|
import { MockManager } from "medusa-test-utils"
|
||||||
|
import { TransactionBaseService } from "../transaction-base-service"
|
||||||
|
|
||||||
describe("BaseService", () => {
|
describe("TransactionBaseService", () => {
|
||||||
it("should cloned the child class withTransaction", () => {
|
it("should cloned the child class withTransaction", () => {
|
||||||
class Child extends BaseService<Child> {
|
class Child extends TransactionBaseService<Child> {
|
||||||
|
protected manager_!: EntityManager
|
||||||
|
protected transactionManager_!: EntityManager
|
||||||
|
|
||||||
constructor(protected readonly container) {
|
constructor(protected readonly container) {
|
||||||
super(container);
|
super(container);
|
||||||
this.container = container
|
this.container = container
|
||||||
@@ -32,30 +35,4 @@ describe("BaseService", () => {
|
|||||||
expect(child2.getTransactionManager()).toBeTruthy()
|
expect(child2.getTransactionManager()).toBeTruthy()
|
||||||
expect((child2.getTransactionManager() as any)?.testProp).toBe('testProp')
|
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"],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
@@ -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<TEntity> = { [key in keyof TEntity]?: unknown }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common functionality for Services
|
|
||||||
* @interface
|
|
||||||
*/
|
|
||||||
export class BaseService<
|
|
||||||
TChild extends BaseService<TChild, TContainer>,
|
|
||||||
TContainer = unknown
|
|
||||||
> {
|
|
||||||
protected transactionManager_: EntityManager | undefined
|
|
||||||
protected manager_: EntityManager
|
|
||||||
private readonly container_: TContainer
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
container: TContainer,
|
|
||||||
protected readonly configModule?: Record<string, unknown>
|
|
||||||
) {
|
|
||||||
this.container_ = container
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to build TypeORM queries.
|
|
||||||
* @param selector The selector
|
|
||||||
* @param config The config
|
|
||||||
* @return The QueryBuilderConfig
|
|
||||||
*/
|
|
||||||
buildQuery_<TEntity = unknown>(
|
|
||||||
selector: Selector<TEntity>,
|
|
||||||
config: FindConfig<TEntity> = {}
|
|
||||||
): FindConfig<TEntity> & {
|
|
||||||
where: Partial<Writable<TEntity>>
|
|
||||||
withDeleted?: boolean
|
|
||||||
} {
|
|
||||||
const build = (
|
|
||||||
obj: Record<string, unknown>
|
|
||||||
): Partial<Writable<TEntity>> => {
|
|
||||||
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<Writable<TEntity>>)
|
|
||||||
}
|
|
||||||
|
|
||||||
const query: FindConfig<TEntity> & {
|
|
||||||
where: Partial<Writable<TEntity>>
|
|
||||||
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<string, unknown> },
|
|
||||||
metadata: Record<string, unknown>
|
|
||||||
): Record<string, unknown> {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from "./tax-calculation-strategy"
|
export * from "./tax-calculation-strategy"
|
||||||
export * from "./cart-completion-strategy"
|
export * from "./cart-completion-strategy"
|
||||||
export * from "./tax-service"
|
export * from "./tax-service"
|
||||||
export * from "./base-service"
|
export * from "./transaction-base-service"
|
||||||
@@ -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"],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { FindConfig, Writable } from "../types/common"
|
||||||
|
import { FindOperator, In, Raw } from "typeorm"
|
||||||
|
|
||||||
|
type Selector<TEntity> = { [key in keyof TEntity]?: unknown }
|
||||||
|
/**
|
||||||
|
* Used to build TypeORM queries.
|
||||||
|
* @param selector The selector
|
||||||
|
* @param config The config
|
||||||
|
* @return The QueryBuilderConfig
|
||||||
|
*/
|
||||||
|
export function buildQuery<TEntity = unknown>(
|
||||||
|
selector: Selector<TEntity>,
|
||||||
|
config: FindConfig<TEntity> = {}
|
||||||
|
): FindConfig<TEntity> & {
|
||||||
|
where: Partial<Writable<TEntity>>
|
||||||
|
withDeleted?: boolean
|
||||||
|
} {
|
||||||
|
const build = (
|
||||||
|
obj: Record<string, unknown>
|
||||||
|
): Partial<Writable<TEntity>> => {
|
||||||
|
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<Writable<TEntity>>)
|
||||||
|
}
|
||||||
|
|
||||||
|
const query: FindConfig<TEntity> & {
|
||||||
|
where: Partial<Writable<TEntity>>
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -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<string, unknown> },
|
||||||
|
metadata: Record<string, unknown>
|
||||||
|
): Record<string, unknown> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in new issue
Block a user