From 7390c14c20f7b5053458de1b1a58870dc8e441cc Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 20 Nov 2024 14:09:44 +0530 Subject: [PATCH] feat: add support for making relationships searchable (#10172) Closes: FRMW-2784 --- .changeset/tough-news-guess.md | 5 + packages/core/types/src/dml/index.ts | 1 + .../dml/__tests__/base-relationship.spec.ts | 1 + .../src/dml/__tests__/entity-builder.spec.ts | 103 ++++++++++++++++++ .../__tests__/has-many-relationship.spec.ts | 1 + .../__tests__/has-one-relationship.spec.ts | 2 + .../src/dml/__tests__/many-to-many.spec.ts | 1 + .../dml/helpers/create-mikro-orm-entity.ts | 1 + .../entity-builder/apply-searchable.ts | 23 +++- packages/core/utils/src/dml/relations/base.ts | 22 ++++ 10 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 .changeset/tough-news-guess.md diff --git a/.changeset/tough-news-guess.md b/.changeset/tough-news-guess.md new file mode 100644 index 0000000000..5b26569054 --- /dev/null +++ b/.changeset/tough-news-guess.md @@ -0,0 +1,5 @@ +--- +"@medusajs/utils": feature +--- + +feat: add support for making relationships searchable diff --git a/packages/core/types/src/dml/index.ts b/packages/core/types/src/dml/index.ts index e53b9196e7..bb6436de5d 100644 --- a/packages/core/types/src/dml/index.ts +++ b/packages/core/types/src/dml/index.ts @@ -106,6 +106,7 @@ export type RelationshipMetadata = { entity: unknown nullable: boolean mappedBy?: string + searchable: boolean options: Record } diff --git a/packages/core/utils/src/dml/__tests__/base-relationship.spec.ts b/packages/core/utils/src/dml/__tests__/base-relationship.spec.ts index 015c9126c9..f8ec7dc96c 100644 --- a/packages/core/utils/src/dml/__tests__/base-relationship.spec.ts +++ b/packages/core/utils/src/dml/__tests__/base-relationship.spec.ts @@ -25,6 +25,7 @@ describe("Base relationship", () => { options: { mappedBy: "user_id" }, mappedBy: "user_id", entity: entityRef, + searchable: false, }) }) }) diff --git a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts index db304de5a9..d15497428a 100644 --- a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts +++ b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts @@ -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", { diff --git a/packages/core/utils/src/dml/__tests__/has-many-relationship.spec.ts b/packages/core/utils/src/dml/__tests__/has-many-relationship.spec.ts index 80f83ecc5b..dff6acb990 100644 --- a/packages/core/utils/src/dml/__tests__/has-many-relationship.spec.ts +++ b/packages/core/utils/src/dml/__tests__/has-many-relationship.spec.ts @@ -16,6 +16,7 @@ describe("HasMany relationship", () => { name: "user", type: "hasMany", nullable: false, + searchable: false, options: {}, entity: entityRef, }) diff --git a/packages/core/utils/src/dml/__tests__/has-one-relationship.spec.ts b/packages/core/utils/src/dml/__tests__/has-one-relationship.spec.ts index b18f87778d..4964364706 100644 --- a/packages/core/utils/src/dml/__tests__/has-one-relationship.spec.ts +++ b/packages/core/utils/src/dml/__tests__/has-one-relationship.spec.ts @@ -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, }) }) diff --git a/packages/core/utils/src/dml/__tests__/many-to-many.spec.ts b/packages/core/utils/src/dml/__tests__/many-to-many.spec.ts index 18bcae8e6c..ce06ed03f0 100644 --- a/packages/core/utils/src/dml/__tests__/many-to-many.spec.ts +++ b/packages/core/utils/src/dml/__tests__/many-to-many.spec.ts @@ -17,6 +17,7 @@ describe("ManyToMany relationship", () => { type: "manyToMany", nullable: false, options: {}, + searchable: false, entity: entityRef, }) }) diff --git a/packages/core/utils/src/dml/helpers/create-mikro-orm-entity.ts b/packages/core/utils/src/dml/helpers/create-mikro-orm-entity.ts index 71be269c34..de7e5120da 100644 --- a/packages/core/utils/src/dml/helpers/create-mikro-orm-entity.ts +++ b/packages/core/utils/src/dml/helpers/create-mikro-orm-entity.ts @@ -85,6 +85,7 @@ export function createMikrORMEntity() { applySearchable(MikroORMEntity, field) } else { defineRelationship(MikroORMEntity, field, cascades, context) + applySearchable(MikroORMEntity, field) } }) diff --git a/packages/core/utils/src/dml/helpers/entity-builder/apply-searchable.ts b/packages/core/utils/src/dml/helpers/entity-builder/apply-searchable.ts index 6bcdbed0b9..d79c7c9e54 100644 --- a/packages/core/utils/src/dml/helpers/entity-builder/apply-searchable.ts +++ b/packages/core/utils/src/dml/helpers/entity-builder/apply-searchable.ts @@ -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, - 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) } diff --git a/packages/core/utils/src/dml/relations/base.ts b/packages/core/utils/src/dml/relations/base.ts index 55af61c929..e29e74f205 100644 --- a/packages/core/utils/src/dml/relations/base.ts +++ b/packages/core/utils/src/dml/relations/base.ts @@ -14,6 +14,7 @@ export const IsRelationship = Symbol.for("isRelationship") export abstract class BaseRelationship implements RelationshipType { [IsRelationship]: true = true + #searchable: boolean = false #referencedEntity: T /** @@ -43,6 +44,26 @@ export abstract class BaseRelationship implements RelationshipType { 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 implements RelationshipType { nullable: false, mappedBy: this.options.mappedBy, options: this.options, + searchable: this.#searchable, entity: this.#referencedEntity, type: this.type, }