feat: add support for making relationships searchable (#10172)
Closes: FRMW-2784
This commit is contained in:
5
.changeset/tough-news-guess.md
Normal file
5
.changeset/tough-news-guess.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/utils": feature
|
||||
---
|
||||
|
||||
feat: add support for making relationships searchable
|
||||
@@ -106,6 +106,7 @@ export type RelationshipMetadata = {
|
||||
entity: unknown
|
||||
nullable: boolean
|
||||
mappedBy?: string
|
||||
searchable: boolean
|
||||
options: Record<string, any>
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ describe("Base relationship", () => {
|
||||
options: { mappedBy: "user_id" },
|
||||
mappedBy: "user_id",
|
||||
entity: entityRef,
|
||||
searchable: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -16,6 +16,7 @@ describe("HasMany relationship", () => {
|
||||
name: "user",
|
||||
type: "hasMany",
|
||||
nullable: false,
|
||||
searchable: false,
|
||||
options: {},
|
||||
entity: entityRef,
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ describe("ManyToMany relationship", () => {
|
||||
type: "manyToMany",
|
||||
nullable: false,
|
||||
options: {},
|
||||
searchable: false,
|
||||
entity: entityRef,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -85,6 +85,7 @@ export function createMikrORMEntity() {
|
||||
applySearchable(MikroORMEntity, field)
|
||||
} else {
|
||||
defineRelationship(MikroORMEntity, field, cascades, context)
|
||||
applySearchable(MikroORMEntity, field)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user