From 885c82ded63e504739bf39a113f820c87c9e2d52 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 12 Dec 2024 10:34:39 +0530 Subject: [PATCH] feat: add support for float properties (#10551) --- .changeset/popular-baboons-allow.md | 6 ++ packages/core/types/src/dml/index.ts | 1 + .../src/dml/__tests__/entity-builder.spec.ts | 88 +++++++++++++++++++ .../src/dml/__tests__/float-property.spec.ts | 19 ++++ packages/core/utils/src/dml/entity-builder.ts | 21 +++++ .../helpers/entity-builder/define-property.ts | 27 ++++++ .../__tests__/entity.spec.ts | 31 +++++++ .../core/utils/src/dml/properties/float.ts | 15 ++++ .../core/utils/src/dml/properties/index.ts | 1 + 9 files changed, 209 insertions(+) create mode 100644 .changeset/popular-baboons-allow.md create mode 100644 packages/core/utils/src/dml/__tests__/float-property.spec.ts create mode 100644 packages/core/utils/src/dml/properties/float.ts diff --git a/.changeset/popular-baboons-allow.md b/.changeset/popular-baboons-allow.md new file mode 100644 index 0000000000000..c0cc7f02878a3 --- /dev/null +++ b/.changeset/popular-baboons-allow.md @@ -0,0 +1,6 @@ +--- +"@medusajs/types": patch +"@medusajs/utils": patch +--- + +feat: add support for float properties diff --git a/packages/core/types/src/dml/index.ts b/packages/core/types/src/dml/index.ts index 7009bc019fcd5..040b5bcdbae70 100644 --- a/packages/core/types/src/dml/index.ts +++ b/packages/core/types/src/dml/index.ts @@ -41,6 +41,7 @@ export type KnownDataTypes = | "enum" | "number" | "bigNumber" + | "float" | "serial" | "dateTime" | "array" 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 713d7769549f1..4880b2795457b 100644 --- a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts +++ b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts @@ -1450,6 +1450,94 @@ describe("Entity builder", () => { }, }) }) + + test("define a float property", () => { + const tax = model.define("tax", { + id: model.number(), + rate: model.float(), + }) + + expect(tax.name).toEqual("Tax") + expect(tax.parse().tableName).toEqual("tax") + + const Tax = toMikroORMEntity(tax) + expectTypeOf(new Tax()).toMatchTypeOf<{ + id: number + rate: number + }>() + + const metaData = MetadataStorage.getMetadataFromDecorator(Tax) + expect(metaData.className).toEqual("Tax") + expect(metaData.path).toEqual("Tax") + + 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, + }, + rate: { + reference: "scalar", + type: "number", + columnType: "real", + name: "rate", + fieldName: "rate", + serializer: Number, + nullable: false, + getter: false, + setter: false, + }, + 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 | relationships", () => { diff --git a/packages/core/utils/src/dml/__tests__/float-property.spec.ts b/packages/core/utils/src/dml/__tests__/float-property.spec.ts new file mode 100644 index 0000000000000..ed8c94b4c50e3 --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/float-property.spec.ts @@ -0,0 +1,19 @@ +import { expectTypeOf } from "expect-type" +import { FloatProperty } from "../properties" + +describe("Float property", () => { + test("create float property type", () => { + const property = new FloatProperty() + + expectTypeOf(property["$dataType"]).toEqualTypeOf() + expect(property.parse("rate")).toEqual({ + fieldName: "rate", + dataType: { + name: "float", + }, + nullable: false, + indexes: [], + relationships: [], + }) + }) +}) diff --git a/packages/core/utils/src/dml/entity-builder.ts b/packages/core/utils/src/dml/entity-builder.ts index 4545171084c4e..95d7e5e658518 100644 --- a/packages/core/utils/src/dml/entity-builder.ts +++ b/packages/core/utils/src/dml/entity-builder.ts @@ -27,6 +27,7 @@ import { HasMany } from "./relations/has-many" import { HasOne } from "./relations/has-one" import { HasOneWithForeignKey } from "./relations/has-one-fk" import { ManyToMany } from "./relations/many-to-many" +import { FloatProperty } from "./properties" /** * The implicit properties added by EntityBuilder in every schema @@ -249,6 +250,26 @@ export class EntityBuilder { return new BigNumberProperty() } + /** + * This method defines a float property that allows for + * values with decimal places + * + * @example + * import { model } from "@medusajs/framework/utils" + * + * const MyCustom = model.define("tax", { + * tax_rate: model.float(), + * // ... + * }) + * + * export default MyCustom + * + * @customNamespace Property Types + */ + float() { + return new FloatProperty() + } + /** * This method defines an autoincrement property. * diff --git a/packages/core/utils/src/dml/helpers/entity-builder/define-property.ts b/packages/core/utils/src/dml/helpers/entity-builder/define-property.ts index f31b1b27f9b6f..9db3bcbfdc17a 100644 --- a/packages/core/utils/src/dml/helpers/entity-builder/define-property.ts +++ b/packages/core/utils/src/dml/helpers/entity-builder/define-property.ts @@ -33,6 +33,7 @@ const COLUMN_TYPES: { dateTime: "timestamptz", number: "integer", bigNumber: "numeric", + float: "real", serial: "number", text: "text", json: "jsonb", @@ -53,6 +54,7 @@ const PROPERTY_TYPES: { dateTime: "date", number: "number", bigNumber: "number", + float: "number", serial: "number", text: "string", json: "any", @@ -277,6 +279,31 @@ export function defineProperty( return } + /** + * Handling serial property separately to set the column type + */ + if (field.dataType.name === "float") { + Property({ + columnType: "real", + type: "number", + nullable: field.nullable, + fieldName: field.fieldName, + /** + * Applying number serializer to convert value back to a + * JavaScript number + */ + serializer: Number, + /** + * MikroORM does not ignore undefined values for default when generating + * the database schema SQL. Conditionally add it here to prevent undefined + * from being set as default value in SQL. + */ + ...(isDefined(field.defaultValue) && { default: field.defaultValue }), + })(MikroORMEntity.prototype, field.fieldName) + + return + } + /** * Define rest of properties */ diff --git a/packages/core/utils/src/dml/integration-tests/__tests__/entity.spec.ts b/packages/core/utils/src/dml/integration-tests/__tests__/entity.spec.ts index 4d7c4750684de..6846ceee6fda7 100644 --- a/packages/core/utils/src/dml/integration-tests/__tests__/entity.spec.ts +++ b/packages/core/utils/src/dml/integration-tests/__tests__/entity.spec.ts @@ -33,6 +33,7 @@ describe("EntityBuilder", () => { id: model.id().primaryKey(), username: model.text(), points: model.number().default(0).nullable(), + tax_rate: model.float().default(0).nullable(), }) ;[User] = toMikroOrmEntities([user]) @@ -86,6 +87,7 @@ describe("EntityBuilder", () => { id: user1.id, username: "User 1", points: 0, + tax_rate: 0, created_at: expect.any(Date), updated_at: expect.any(Date), deleted_at: null, @@ -112,6 +114,7 @@ describe("EntityBuilder", () => { id: user1.id, username: "User 1", points: null, + tax_rate: 0, created_at: expect.any(Date), updated_at: expect.any(Date), deleted_at: null, @@ -139,6 +142,34 @@ describe("EntityBuilder", () => { id: user1.id, username: "User 1", points: null, + tax_rate: 0, + created_at: expect.any(Date), + updated_at: expect.any(Date), + deleted_at: null, + }) + }) + + it("set the tax rate as a float value", async () => { + let manager = orm.em.fork() + + const user1 = manager.create(User, { + username: "User 1", + tax_rate: 1.2122, + }) + expect(user1.tax_rate).toEqual(1.2122) + + await manager.persistAndFlush([user1]) + manager = orm.em.fork() + + const user = await manager.findOne(User, { + id: user1.id, + }) + + expect(await mikroOrmSerializer(user)).toEqual({ + id: user1.id, + username: "User 1", + points: 0, + tax_rate: 1.2122, created_at: expect.any(Date), updated_at: expect.any(Date), deleted_at: null, diff --git a/packages/core/utils/src/dml/properties/float.ts b/packages/core/utils/src/dml/properties/float.ts new file mode 100644 index 0000000000000..9eeab10208b1f --- /dev/null +++ b/packages/core/utils/src/dml/properties/float.ts @@ -0,0 +1,15 @@ +import { BaseProperty } from "./base" + +/** + * The FloatProperty is used to define values with decimal + * places. + */ +export class FloatProperty extends BaseProperty { + protected dataType = { + name: "float", + } as const + + static isFloatProperty(obj: any): obj is FloatProperty { + return obj?.dataType?.name === "float" + } +} diff --git a/packages/core/utils/src/dml/properties/index.ts b/packages/core/utils/src/dml/properties/index.ts index 5d85ca547ca93..7c08efe19e81e 100644 --- a/packages/core/utils/src/dml/properties/index.ts +++ b/packages/core/utils/src/dml/properties/index.ts @@ -9,5 +9,6 @@ export * from "./id" export * from "./json" export * from "./nullable" export * from "./number" +export * from "./float" export * from "./primary-key" export * from "./text"