From 39038ddb0a81a747fa9353992870921a08c151d2 Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Tue, 25 Jun 2024 18:00:39 +0200 Subject: [PATCH] chore: various DML improvements (#7833) * chore: various DML improvements * Check is something through static utils * Check is something through static utils * allow to define a schema with table name * restrict searchable to text only * rm searchable modifier * extract constructor logic into separate function --- .../src/dml/__tests__/base-property.spec.ts | 20 + .../src/dml/__tests__/entity-builder.spec.ts | 362 +++++++++++++++++- .../__tests__/has-many-relationship.spec.ts | 15 + .../__tests__/has-one-relationship.spec.ts | 15 + .../src/dml/__tests__/many-to-many.spec.ts | 15 + .../src/dml/__tests__/text-property.spec.ts | 2 +- packages/core/utils/src/dml/entity-builder.ts | 9 +- packages/core/utils/src/dml/entity.ts | 54 ++- .../dml/helpers/create-mikro-orm-entity.ts | 58 +-- .../create-big-number-properties.ts | 4 +- .../utils/src/dml/properties/big-number.ts | 4 + .../core/utils/src/dml/properties/nullable.ts | 6 + .../core/utils/src/dml/properties/text.ts | 11 +- .../utils/src/dml/relations/belongs-to.ts | 4 + .../core/utils/src/dml/relations/has-many.ts | 4 + .../core/utils/src/dml/relations/has-one.ts | 4 + .../utils/src/dml/relations/many-to-many.ts | 4 + .../core/utils/src/dml/relations/nullable.ts | 10 + 18 files changed, 563 insertions(+), 38 deletions(-) diff --git a/packages/core/utils/src/dml/__tests__/base-property.spec.ts b/packages/core/utils/src/dml/__tests__/base-property.spec.ts index f882f218c0..afc0f69db2 100644 --- a/packages/core/utils/src/dml/__tests__/base-property.spec.ts +++ b/packages/core/utils/src/dml/__tests__/base-property.spec.ts @@ -1,6 +1,7 @@ import { expectTypeOf } from "expect-type" import { BaseProperty } from "../properties/base" import { PropertyMetadata } from "@medusajs/types" +import { TextProperty } from "../properties/text" describe("Base property", () => { test("create a property type from base property", () => { @@ -24,6 +25,25 @@ describe("Base property", () => { }) }) + test("apply searchable modifier", () => { + const property = new TextProperty().searchable() + + expectTypeOf(property["$dataType"]).toEqualTypeOf() + expect(property.parse("username")).toEqual({ + fieldName: "username", + dataType: { + name: "text", + options: { + primaryKey: false, + searchable: true, + }, + }, + nullable: false, + indexes: [], + relationships: [], + }) + }) + test("apply nullable modifier", () => { class StringProperty extends BaseProperty { protected dataType: PropertyMetadata["dataType"] = { 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 920a007c33..9e1a9b09d7 100644 --- a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts +++ b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts @@ -37,6 +37,9 @@ describe("Entity builder", () => { spend_limit: model.bigNumber(), }) + expect(user.name).toEqual("user") + expect(user.parse().tableName).toEqual("user") + const User = toMikroORMEntity(user) expectTypeOf(new User()).toMatchTypeOf<{ @@ -145,6 +148,250 @@ describe("Entity builder", () => { }) }) + test("define an entity with a table name and a name", () => { + const user = model.define( + { name: "user", tableName: "user_table" }, + { + id: model.number(), + username: model.text(), + email: model.text(), + spend_limit: model.bigNumber(), + } + ) + + expect(user.name).toEqual("user") + expect(user.parse().tableName).toEqual("user_table") + + const User = toMikroORMEntity(user) + + expectTypeOf(new User()).toMatchTypeOf<{ + id: number + username: string + email: string + spend_limit: number + raw_spend_limit: Record + created_at: Date + updated_at: Date + 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", + nullable: false, + getter: false, + setter: false, + }, + username: { + reference: "scalar", + type: "string", + columnType: "text", + name: "username", + nullable: false, + getter: false, + setter: false, + }, + email: { + reference: "scalar", + type: "string", + columnType: "text", + name: "email", + nullable: false, + getter: false, + setter: false, + }, + created_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "created_at", + defaultRaw: "now()", + onCreate: expect.any(Function), + nullable: false, + getter: false, + setter: false, + }, + spend_limit: { + columnType: "numeric", + getter: true, + name: "spend_limit", + nullable: false, + reference: "scalar", + setter: true, + trackChanges: false, + type: "any", + }, + raw_spend_limit: { + columnType: "jsonb", + getter: false, + name: "raw_spend_limit", + nullable: false, + reference: "scalar", + setter: false, + type: "any", + }, + updated_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "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", + nullable: true, + getter: false, + setter: false, + }, + }) + }) + + test("define an entity with a table name only", () => { + const user = model.define( + { tableName: "user_role" }, + { + id: model.number(), + username: model.text(), + email: model.text(), + spend_limit: model.bigNumber(), + } + ) + + expect(user.name).toEqual("userRole") + expect(user.parse().tableName).toEqual("user_role") + + const User = toMikroORMEntity(user) + + expectTypeOf(new User()).toMatchTypeOf<{ + id: number + username: string + email: string + spend_limit: number + raw_spend_limit: Record + created_at: Date + updated_at: Date + deleted_at: Date | null + }>() + + const metaData = MetadataStorage.getMetadataFromDecorator(User) + expect(metaData.className).toEqual("UserRole") + expect(metaData.path).toEqual("UserRole") + + 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", + nullable: false, + getter: false, + setter: false, + }, + username: { + reference: "scalar", + type: "string", + columnType: "text", + name: "username", + nullable: false, + getter: false, + setter: false, + }, + email: { + reference: "scalar", + type: "string", + columnType: "text", + name: "email", + nullable: false, + getter: false, + setter: false, + }, + created_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "created_at", + defaultRaw: "now()", + onCreate: expect.any(Function), + nullable: false, + getter: false, + setter: false, + }, + spend_limit: { + columnType: "numeric", + getter: true, + name: "spend_limit", + nullable: false, + reference: "scalar", + setter: true, + trackChanges: false, + type: "any", + }, + raw_spend_limit: { + columnType: "jsonb", + getter: false, + name: "raw_spend_limit", + nullable: false, + reference: "scalar", + setter: false, + type: "any", + }, + updated_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "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", + nullable: true, + getter: false, + setter: false, + }, + }) + }) + test("define a property with default value", () => { const user = model.define("user", { id: model.number(), @@ -258,6 +505,119 @@ describe("Entity builder", () => { }) }) + test("should mark a property as searchable", () => { + const user = model.define("user", { + id: model.number(), + username: model.text().searchable(), + email: model.text(), + spend_limit: model.bigNumber().default(500.4), + }) + + const User = toMikroORMEntity(user) + expectTypeOf(new User()).toMatchTypeOf<{ + id: number + username: string + 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", + nullable: false, + getter: false, + setter: false, + }, + username: { + reference: "scalar", + type: "string", + columnType: "text", + name: "username", + nullable: false, + getter: false, + setter: false, + searchable: true, + }, + email: { + reference: "scalar", + type: "string", + columnType: "text", + name: "email", + nullable: false, + getter: false, + setter: false, + }, + spend_limit: { + columnType: "numeric", + default: 500.4, + getter: true, + name: "spend_limit", + nullable: false, + reference: "scalar", + setter: true, + trackChanges: false, + type: "any", + }, + raw_spend_limit: { + columnType: "jsonb", + getter: false, + name: "raw_spend_limit", + nullable: false, + reference: "scalar", + setter: false, + type: "any", + }, + created_at: { + reference: "scalar", + type: "date", + columnType: "timestamptz", + name: "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", + 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", + nullable: true, + getter: false, + setter: false, + }, + }) + }) + test("mark property nullable", () => { const user = model.define("user", { id: model.number(), @@ -2472,7 +2832,7 @@ describe("Entity builder", () => { email: model.hasOne(() => email), }) - const [User, Email] = [user, email].map(toMikroORMEntity) + const [User, Email] = toMikroOrmEntities([user, email]) expectTypeOf(new User()).toMatchTypeOf<{ id: number 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 f1c0a7eb3c..80f83ecc5b 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 @@ -20,4 +20,19 @@ describe("HasMany relationship", () => { entity: entityRef, }) }) + + test("should identify has many relationship", () => { + const user = { + username: new TextProperty(), + } + + const entityRef = () => user + let relationship = new HasMany(entityRef, {}) + + expect(HasMany.isHasMany(relationship)).toEqual(true) + + relationship = {} as any + + expect(HasMany.isHasMany(relationship)).toEqual(false) + }) }) 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 57fedb3908..b18f87778d 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 @@ -40,4 +40,19 @@ describe("HasOne relationship", () => { entity: entityRef, }) }) + + test("should identify has one relationship", () => { + const user = { + username: new TextProperty(), + } + + const entityRef = () => user + let relationship = new HasOne(entityRef, {}) + + expect(HasOne.isHasOne(relationship)).toEqual(true) + + relationship = {} as any + + expect(HasOne.isHasOne(relationship)).toEqual(false) + }) }) 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 90fd733d5d..18bcae8e6c 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 @@ -20,4 +20,19 @@ describe("ManyToMany relationship", () => { entity: entityRef, }) }) + + test("should identify many to many relationship", () => { + const user = { + username: new TextProperty(), + } + + const entityRef = () => user + let relationship = new ManyToMany(entityRef, {}) + + expect(ManyToMany.isManyToMany(relationship)).toEqual(true) + + relationship = {} as any + + expect(ManyToMany.isManyToMany(relationship)).toEqual(false) + }) }) diff --git a/packages/core/utils/src/dml/__tests__/text-property.spec.ts b/packages/core/utils/src/dml/__tests__/text-property.spec.ts index 98d792621e..9d7e34c223 100644 --- a/packages/core/utils/src/dml/__tests__/text-property.spec.ts +++ b/packages/core/utils/src/dml/__tests__/text-property.spec.ts @@ -10,7 +10,7 @@ describe("Text property", () => { fieldName: "username", dataType: { name: "text", - options: { primaryKey: false }, + options: { primaryKey: false, searchable: false }, }, nullable: false, indexes: [], diff --git a/packages/core/utils/src/dml/entity-builder.ts b/packages/core/utils/src/dml/entity-builder.ts index f2afac4237..88a60be8b3 100644 --- a/packages/core/utils/src/dml/entity-builder.ts +++ b/packages/core/utils/src/dml/entity-builder.ts @@ -29,6 +29,8 @@ export type DMLSchema = Record< PropertyType | RelationshipType > +type DefineOptions = string | { name?: string; tableName: string } + /** * Entity builder exposes the API to create an entity and define its * schema using the shorthand methods. @@ -52,10 +54,13 @@ export class EntityBuilder { * Define an entity or a model. The name should be unique across * all the entities. */ - define(name: string, schema: Schema) { + define( + nameOrConfig: DefineOptions, + schema: Schema + ) { this.#disallowImplicitProperties(schema) - return new DmlEntity(name, { + return new DmlEntity(nameOrConfig, { ...schema, ...createBigNumberProperties(schema), ...createDefaultProperties(), diff --git a/packages/core/utils/src/dml/entity.ts b/packages/core/utils/src/dml/entity.ts index 4590bde46a..12070f01ca 100644 --- a/packages/core/utils/src/dml/entity.ts +++ b/packages/core/utils/src/dml/entity.ts @@ -8,6 +8,40 @@ import { } from "@medusajs/types" import { DMLSchema } from "./entity-builder" import { BelongsTo } from "./relations/belongs-to" +import { isObject, isString, toCamelCase } from "../common" + +type Config = string | { name?: string; tableName: string } + +function extractNameAndTableName(nameOrConfig: Config) { + const result = { + name: "", + tableName: "", + } + + if (isString(nameOrConfig)) { + const [schema, ...rest] = nameOrConfig.split(".") + const name = rest.length ? rest.join(".") : schema + result.name = toCamelCase(name) + result.tableName = nameOrConfig + } + + if (isObject(nameOrConfig)) { + if (!nameOrConfig.tableName) { + throw new Error( + `Missing "tableName" property in the config object for "${nameOrConfig.name}" entity` + ) + } + + const potentialName = nameOrConfig.name ?? nameOrConfig.tableName + const [schema, ...rest] = potentialName.split(".") + const name = rest.length ? rest.join(".") : schema + + result.name = toCamelCase(name) + result.tableName = nameOrConfig.tableName + } + + return result +} /** * Dml entity is a representation of a DML model with a unique @@ -16,8 +50,16 @@ import { BelongsTo } from "./relations/belongs-to" export class DmlEntity implements IDmlEntity { [IsDmlEntity]: true = true + name: string + + readonly #tableName: string #cascades: EntityCascades = {} - constructor(public name: string, public schema: Schema) {} + + constructor(nameOrConfig: Config, public schema: Schema) { + const { name, tableName } = extractNameAndTableName(nameOrConfig) + this.name = name + this.#tableName = tableName + } /** * A static method to check if an entity is an instance of DmlEntity. @@ -27,11 +69,7 @@ export class DmlEntity implements IDmlEntity { * @param entity */ static isDmlEntity(entity: unknown): entity is DmlEntity { - return ( - !!entity && - (entity instanceof DmlEntity || - (typeof entity === "object" && entity[IsDmlEntity] === true)) - ) + return !!entity?.[IsDmlEntity] } /** @@ -39,11 +77,13 @@ export class DmlEntity implements IDmlEntity { */ parse(): { name: string + tableName: string schema: PropertyType | RelationshipType cascades: EntityCascades } { return { name: this.name, + tableName: this.#tableName, schema: this.schema as unknown as | PropertyType | RelationshipType, @@ -63,7 +103,7 @@ export class DmlEntity implements IDmlEntity { > ) { const childToParentCascades = options.delete?.filter((relationship) => { - return this.schema[relationship] instanceof BelongsTo + return BelongsTo.isBelongsTo(this.schema[relationship]) }) if (childToParentCascades?.length) { 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 62b3d327bf..dcc117a419 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 @@ -33,6 +33,7 @@ import { upperCaseFirst } from "../../common/upper-case-first" import { MikroOrmBigNumberProperty, mikroOrmSoftDeletableFilterOptions, + Searchable, } from "../../dal" import { DmlEntity } from "../entity" import { HasMany } from "../relations/has-many" @@ -118,14 +119,16 @@ export function createMikrORMEntity() { * Parses entity name and returns model and table name from * it */ - function parseEntityName(entityName: string) { + function parseEntityName(entity: DmlEntity) { + const parsedEntity = entity.parse() + /** * Table name is going to be the snake case version of the entity name. * Here we should preserve PG schema (if defined). * * For example: "platform.user" should stay as "platform.user" */ - const tableName = camelToSnakeCase(entityName) + const tableName = camelToSnakeCase(parsedEntity.tableName) /** * Entity name is going to be the camelCase version of the @@ -134,9 +137,7 @@ export function createMikrORMEntity() { const [pgSchema, ...rest] = tableName.split(".") return { tableName, - modelName: upperCaseFirst( - toCamelCase(rest.length ? rest.join("_") : pgSchema) - ), + modelName: upperCaseFirst(toCamelCase(parsedEntity.name)), pgSchema: rest.length ? pgSchema : undefined, } } @@ -308,6 +309,20 @@ export function createMikrORMEntity() { }) } + /** + * Apply the searchable decorator to the property marked as searchable to enable the free text search + */ + function applySearchable( + MikroORMEntity: EntityConstructor, + field: PropertyMetadata + ) { + if (!field.dataType.options?.searchable) { + return + } + + Searchable()(MikroORMEntity.prototype, field.fieldName) + } + /** * Defines has one relationship on the Mikro ORM entity. */ @@ -412,8 +427,8 @@ export function createMikrORMEntity() { * Otherside is a has many. Hence we should defined a ManyToOne */ if ( - otherSideRelation instanceof HasMany || - otherSideRelation instanceof DmlManyToMany + HasMany.isHasMany(otherSideRelation) || + DmlManyToMany.isManyToMany(otherSideRelation) ) { const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`) @@ -426,7 +441,7 @@ export function createMikrORMEntity() { onDelete: shouldCascade ? "cascade" : undefined, })(MikroORMEntity.prototype, camelToSnakeCase(`${relationship.name}Id`)) - if (otherSideRelation instanceof DmlManyToMany) { + if (DmlManyToMany.isManyToMany(otherSideRelation)) { Property({ type: relatedModelName, persist: false, @@ -448,7 +463,7 @@ export function createMikrORMEntity() { /** * Otherside is a has one. Hence we should defined a OneToOne */ - if (otherSideRelation instanceof HasOne) { + if (HasOne.isHasOne(otherSideRelation)) { const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`) OneToOne({ @@ -516,7 +531,7 @@ export function createMikrORMEntity() { ) } - if (otherSideRelation instanceof DmlManyToMany === false) { + if (!DmlManyToMany.isManyToMany(otherSideRelation)) { throw new Error( `Invalid relationship reference for "${mappedBy}" on "${relatedModelName}" entity. Make sure to define a manyToMany relationship` ) @@ -554,13 +569,13 @@ export function createMikrORMEntity() { } const pivotEntity = relationship.options.pivotEntity() - if (!(pivotEntity instanceof DmlEntity)) { + if (!DmlEntity.isDmlEntity(pivotEntity)) { throw new Error( `Invalid pivotEntity reference for "${MikroORMEntity.name}.${relationship.name}". Make sure to return a DML entity from the pivotEntity callback` ) } - pivotEntityName = parseEntityName(pivotEntity.parse().name).modelName + pivotEntityName = parseEntityName(pivotEntity).modelName } if (!pivotEntityName) { @@ -630,15 +645,13 @@ export function createMikrORMEntity() { /** * Ensure the return value is a DML entity instance */ - if (!(relatedEntity instanceof DmlEntity)) { + if (!DmlEntity.isDmlEntity(relatedEntity)) { throw new Error( `Invalid relationship reference for "${MikroORMEntity.name}.${relationship.name}". Make sure to return a DML entity from the relationship callback` ) } - const { modelName, tableName, pgSchema } = parseEntityName( - relatedEntity.parse().name - ) + const { modelName, tableName, pgSchema } = parseEntityName(relatedEntity) const relatedEntityInfo = { relatedModelName: modelName, relatedTableName: tableName, @@ -691,8 +704,8 @@ export function createMikrORMEntity() { return function createEntity>(entity: T): Infer { class MikroORMEntity {} - const { name, schema, cascades } = entity.parse() - const { modelName, tableName } = parseEntityName(name) + const { schema, cascades } = entity.parse() + const { modelName, tableName } = parseEntityName(entity) /** * Assigning name to the class constructor, so that it matches @@ -713,6 +726,7 @@ export function createMikrORMEntity() { if ("fieldName" in field) { defineProperty(MikroORMEntity, field) applyIndexes(MikroORMEntity, tableName, field) + applySearchable(MikroORMEntity, field) } else { defineRelationship(MikroORMEntity, field, cascades) } @@ -734,16 +748,14 @@ export function createMikrORMEntity() { */ export const toMikroORMEntity = ( entity: T -): T extends DmlEntity ? EntityConstructor : T => { +): T extends DmlEntity ? Infer : T => { let mikroOrmEntity: T | EntityConstructor = entity if (DmlEntity.isDmlEntity(entity)) { mikroOrmEntity = createMikrORMEntity()(entity) } - return mikroOrmEntity as T extends DmlEntity - ? EntityConstructor - : T + return mikroOrmEntity as T extends DmlEntity ? Infer : T } /** @@ -761,6 +773,6 @@ export const toMikroOrmEntities = function (entities: T) { return entity }) as { - [K in keyof T]: T[K] extends DmlEntity ? EntityConstructor : T[K] + [K in keyof T]: T[K] extends DmlEntity ? Infer : T[K] } } diff --git a/packages/core/utils/src/dml/helpers/entity-builder/create-big-number-properties.ts b/packages/core/utils/src/dml/helpers/entity-builder/create-big-number-properties.ts index 02701473f9..dcd525d489 100644 --- a/packages/core/utils/src/dml/helpers/entity-builder/create-big-number-properties.ts +++ b/packages/core/utils/src/dml/helpers/entity-builder/create-big-number-properties.ts @@ -32,8 +32,8 @@ export function createBigNumberProperties( for (const [key, property] of Object.entries(schema)) { if ( - property instanceof BigNumberProperty || - property instanceof NullableModifier + BigNumberProperty.isBigNumberProperty(property) || + NullableModifier.isNullableModifier(property) ) { const parsed = property.parse(key) diff --git a/packages/core/utils/src/dml/properties/big-number.ts b/packages/core/utils/src/dml/properties/big-number.ts index 2ab2950df3..76ebe9aef2 100644 --- a/packages/core/utils/src/dml/properties/big-number.ts +++ b/packages/core/utils/src/dml/properties/big-number.ts @@ -8,4 +8,8 @@ export class BigNumberProperty extends BaseProperty { protected dataType = { name: "bigNumber", } as const + + static isBigNumberProperty(obj: any): obj is BigNumberProperty { + return obj?.dataType?.name === "bigNumber" + } } diff --git a/packages/core/utils/src/dml/properties/nullable.ts b/packages/core/utils/src/dml/properties/nullable.ts index 0b80d6702a..685addae02 100644 --- a/packages/core/utils/src/dml/properties/nullable.ts +++ b/packages/core/utils/src/dml/properties/nullable.ts @@ -1,11 +1,17 @@ import { PropertyType } from "@medusajs/types" +const IsNullableModifier = Symbol.for("isNullableModifier") /** * Nullable modifier marks a schema node as nullable */ export class NullableModifier> implements PropertyType { + [IsNullableModifier]: true = true + + static isNullableModifier(obj: any): obj is NullableModifier { + return !!obj?.[IsNullableModifier] + } /** * A type-only property to infer the JavScript data-type * of the schema property diff --git a/packages/core/utils/src/dml/properties/text.ts b/packages/core/utils/src/dml/properties/text.ts index 7a06be262c..8d07e487ce 100644 --- a/packages/core/utils/src/dml/properties/text.ts +++ b/packages/core/utils/src/dml/properties/text.ts @@ -8,6 +8,7 @@ export class TextProperty extends BaseProperty { name: "text" options: { primaryKey: boolean + searchable: boolean } } @@ -17,12 +18,18 @@ export class TextProperty extends BaseProperty { return this } - constructor(options?: { primaryKey?: boolean }) { + searchable() { + this.dataType.options.searchable = true + + return this + } + + constructor(options?: { primaryKey?: boolean; searchable?: boolean }) { super() this.dataType = { name: "text", - options: { primaryKey: false, ...options }, + options: { primaryKey: false, searchable: false, ...options }, } } } diff --git a/packages/core/utils/src/dml/relations/belongs-to.ts b/packages/core/utils/src/dml/relations/belongs-to.ts index ca84a4956b..e0187cb381 100644 --- a/packages/core/utils/src/dml/relations/belongs-to.ts +++ b/packages/core/utils/src/dml/relations/belongs-to.ts @@ -4,6 +4,10 @@ import { NullableModifier } from "./nullable" export class BelongsTo extends BaseRelationship { type = "belongsTo" as const + static isBelongsTo(relationship: any): relationship is BelongsTo { + return relationship?.type === "belongsTo" + } + /** * Apply nullable modifier on the schema */ diff --git a/packages/core/utils/src/dml/relations/has-many.ts b/packages/core/utils/src/dml/relations/has-many.ts index 2afff2be55..e3ba1e2f67 100644 --- a/packages/core/utils/src/dml/relations/has-many.ts +++ b/packages/core/utils/src/dml/relations/has-many.ts @@ -12,4 +12,8 @@ import { BaseRelationship } from "./base" */ export class HasMany extends BaseRelationship { type = "hasMany" as const + + static isHasMany(relationship: any): relationship is HasMany { + return relationship?.type === "hasMany" + } } diff --git a/packages/core/utils/src/dml/relations/has-one.ts b/packages/core/utils/src/dml/relations/has-one.ts index 766cb8b7fe..7d080ec426 100644 --- a/packages/core/utils/src/dml/relations/has-one.ts +++ b/packages/core/utils/src/dml/relations/has-one.ts @@ -14,6 +14,10 @@ import { NullableModifier } from "./nullable" export class HasOne extends BaseRelationship { type = "hasOne" as const + static isHasOne(relationship: any): relationship is HasOne { + return relationship?.type === "hasOne" + } + /** * Apply nullable modifier on the schema */ diff --git a/packages/core/utils/src/dml/relations/many-to-many.ts b/packages/core/utils/src/dml/relations/many-to-many.ts index 19154071e9..9c220ab457 100644 --- a/packages/core/utils/src/dml/relations/many-to-many.ts +++ b/packages/core/utils/src/dml/relations/many-to-many.ts @@ -13,4 +13,8 @@ import { BaseRelationship } from "./base" */ export class ManyToMany extends BaseRelationship { type = "manyToMany" as const + + static isManyToMany(relationship: any): relationship is ManyToMany { + return relationship?.type === "manyToMany" + } } diff --git a/packages/core/utils/src/dml/relations/nullable.ts b/packages/core/utils/src/dml/relations/nullable.ts index 80e56dade0..dfa0f081f1 100644 --- a/packages/core/utils/src/dml/relations/nullable.ts +++ b/packages/core/utils/src/dml/relations/nullable.ts @@ -1,11 +1,21 @@ import { RelationshipType } from "@medusajs/types" +const IsNullableModifier = Symbol.for("isNullableModifier") + /** * Nullable modifier marks a schema node as nullable */ export class NullableModifier> implements RelationshipType { + [IsNullableModifier]: true = true + + static isNullableModifier( + modifier: any + ): modifier is NullableModifier { + return !!modifier?.[IsNullableModifier] + } + declare type: RelationshipType["type"] /**