feat: add support for making relationships searchable (#10172)
Closes: FRMW-2784
This commit is contained in:
1 parent
495c381709
commit
7390c14c20
10 files changed
+156
-4
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/utils": feature
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: add support for making relationships searchable
|
||||||
@@ -106,6 +106,7 @@ export type RelationshipMetadata = {
|
|||||||
entity: unknown
|
entity: unknown
|
||||||
nullable: boolean
|
nullable: boolean
|
||||||
mappedBy?: string
|
mappedBy?: string
|
||||||
|
searchable: boolean
|
||||||
options: Record<string, any>
|
options: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ describe("Base relationship", () => {
|
|||||||
options: { mappedBy: "user_id" },
|
options: { mappedBy: "user_id" },
|
||||||
mappedBy: "user_id",
|
mappedBy: "user_id",
|
||||||
entity: entityRef,
|
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", () => {
|
describe("Entity builder | id", () => {
|
||||||
test("define an entity with id property", () => {
|
test("define an entity with id property", () => {
|
||||||
const user = model.define("user", {
|
const user = model.define("user", {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ describe("HasMany relationship", () => {
|
|||||||
name: "user",
|
name: "user",
|
||||||
type: "hasMany",
|
type: "hasMany",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
searchable: false,
|
||||||
options: {},
|
options: {},
|
||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ describe("HasOne relationship", () => {
|
|||||||
type: "hasOne",
|
type: "hasOne",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
options: {},
|
options: {},
|
||||||
|
searchable: false,
|
||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -37,6 +38,7 @@ describe("HasOne relationship", () => {
|
|||||||
type: "hasOne",
|
type: "hasOne",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
options: {},
|
options: {},
|
||||||
|
searchable: false,
|
||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ describe("ManyToMany relationship", () => {
|
|||||||
type: "manyToMany",
|
type: "manyToMany",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
options: {},
|
options: {},
|
||||||
|
searchable: false,
|
||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export function createMikrORMEntity() {
|
|||||||
applySearchable(MikroORMEntity, field)
|
applySearchable(MikroORMEntity, field)
|
||||||
} else {
|
} else {
|
||||||
defineRelationship(MikroORMEntity, field, cascades, context)
|
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"
|
import { Searchable } from "../../../dal"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6,11 +10,22 @@ import { Searchable } from "../../../dal"
|
|||||||
*/
|
*/
|
||||||
export function applySearchable(
|
export function applySearchable(
|
||||||
MikroORMEntity: EntityConstructor<any>,
|
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
|
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> {
|
export abstract class BaseRelationship<T> implements RelationshipType<T> {
|
||||||
[IsRelationship]: true = true
|
[IsRelationship]: true = true
|
||||||
|
|
||||||
|
#searchable: boolean = false
|
||||||
#referencedEntity: T
|
#referencedEntity: T
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +44,26 @@ export abstract class BaseRelationship<T> implements RelationshipType<T> {
|
|||||||
this.options = options
|
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
|
* Returns the parsed copy of the relationship
|
||||||
*/
|
*/
|
||||||
@@ -52,6 +73,7 @@ export abstract class BaseRelationship<T> implements RelationshipType<T> {
|
|||||||
nullable: false,
|
nullable: false,
|
||||||
mappedBy: this.options.mappedBy,
|
mappedBy: this.options.mappedBy,
|
||||||
options: this.options,
|
options: this.options,
|
||||||
|
searchable: this.#searchable,
|
||||||
entity: this.#referencedEntity,
|
entity: this.#referencedEntity,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user