feat: add support for making relationships searchable (#10172)

Closes: FRMW-2784
This commit is contained in:
Harminder Virk
2024-11-20 14:09:44 +05:30
committed by GitHub
parent 495c381709
commit 7390c14c20
10 changed files with 156 additions and 4 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/utils": feature
---
feat: add support for making relationships searchable

View File

@@ -106,6 +106,7 @@ export type RelationshipMetadata = {
entity: unknown
nullable: boolean
mappedBy?: string
searchable: boolean
options: Record<string, any>
}

View File

@@ -25,6 +25,7 @@ describe("Base relationship", () => {
options: { mappedBy: "user_id" },
mappedBy: "user_id",
entity: entityRef,
searchable: false,
})
})
})

View File

@@ -1449,6 +1449,109 @@ describe("Entity builder", () => {
})
})
describe("Entity builder | relationships", () => {
test("should mark a relationship as searchable", () => {
const user = model.define("user", {
id: model.number(),
username: model.text(),
emails: model.hasMany(() => email).searchable(),
})
const email = model.define("email", {
id: model.number(),
email: model.text(),
})
const User = toMikroORMEntity(user)
expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
emails: { id: number; email: string }[]
deleted_at: Date | null
}>()
const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.filters).toEqual({
softDeletable: {
name: "softDeletable",
cond: expect.any(Function),
default: true,
args: false,
},
})
expect(metaData.properties).toEqual({
id: {
reference: "scalar",
type: "number",
columnType: "integer",
name: "id",
fieldName: "id",
nullable: false,
getter: false,
setter: false,
},
username: {
reference: "scalar",
type: "string",
columnType: "text",
name: "username",
fieldName: "username",
nullable: false,
getter: false,
setter: false,
},
emails: {
cascade: undefined,
entity: "Email",
mappedBy: "user",
name: "emails",
orphanRemoval: true,
reference: "1:m",
searchable: true,
},
created_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "created_at",
fieldName: "created_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
updated_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "updated_at",
fieldName: "updated_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
onUpdate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
deleted_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "deleted_at",
fieldName: "deleted_at",
nullable: true,
getter: false,
setter: false,
},
})
})
})
describe("Entity builder | id", () => {
test("define an entity with id property", () => {
const user = model.define("user", {

View File

@@ -16,6 +16,7 @@ describe("HasMany relationship", () => {
name: "user",
type: "hasMany",
nullable: false,
searchable: false,
options: {},
entity: entityRef,
})

View File

@@ -17,6 +17,7 @@ describe("HasOne relationship", () => {
type: "hasOne",
nullable: false,
options: {},
searchable: false,
entity: entityRef,
})
})
@@ -37,6 +38,7 @@ describe("HasOne relationship", () => {
type: "hasOne",
nullable: true,
options: {},
searchable: false,
entity: entityRef,
})
})

View File

@@ -17,6 +17,7 @@ describe("ManyToMany relationship", () => {
type: "manyToMany",
nullable: false,
options: {},
searchable: false,
entity: entityRef,
})
})

View File

@@ -85,6 +85,7 @@ export function createMikrORMEntity() {
applySearchable(MikroORMEntity, field)
} else {
defineRelationship(MikroORMEntity, field, cascades, context)
applySearchable(MikroORMEntity, field)
}
})

View File

@@ -1,4 +1,8 @@
import { EntityConstructor, PropertyMetadata } from "@medusajs/types"
import {
EntityConstructor,
PropertyMetadata,
RelationshipMetadata,
} from "@medusajs/types"
import { Searchable } from "../../../dal"
/**
@@ -6,11 +10,22 @@ import { Searchable } from "../../../dal"
*/
export function applySearchable(
MikroORMEntity: EntityConstructor<any>,
field: PropertyMetadata
fieldOrRelationship: PropertyMetadata | RelationshipMetadata
) {
if (!field.dataType.options?.searchable) {
let propertyName: string
let isSearchable: boolean
if ("fieldName" in fieldOrRelationship) {
propertyName = fieldOrRelationship.fieldName
isSearchable = !!fieldOrRelationship.dataType.options?.searchable
} else {
propertyName = fieldOrRelationship.name
isSearchable = fieldOrRelationship.searchable
}
if (!isSearchable) {
return
}
Searchable()(MikroORMEntity.prototype, field.fieldName)
Searchable()(MikroORMEntity.prototype, propertyName)
}

View File

@@ -14,6 +14,7 @@ export const IsRelationship = Symbol.for("isRelationship")
export abstract class BaseRelationship<T> implements RelationshipType<T> {
[IsRelationship]: true = true
#searchable: boolean = false
#referencedEntity: T
/**
@@ -43,6 +44,26 @@ export abstract class BaseRelationship<T> implements RelationshipType<T> {
this.options = options
}
/**
* This method indicates that the relationship is searchable
*
* @example
* import { model } from "@medusajs/framework/utils"
*
* const Product = model.define("Product", {
* variants: model.hasMany(() => ProductVariant).searchable(),
* // ...
* })
*
* export default Product
*
* @customNamespace Property Configuration Methods
*/
searchable() {
this.#searchable = true
return this
}
/**
* Returns the parsed copy of the relationship
*/
@@ -52,6 +73,7 @@ export abstract class BaseRelationship<T> implements RelationshipType<T> {
nullable: false,
mappedBy: this.options.mappedBy,
options: this.options,
searchable: this.#searchable,
entity: this.#referencedEntity,
type: this.type,
}