From 074e4a888e84f3865ed88abee5aba56ca7a07d90 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 2 Jul 2024 13:27:21 +0530 Subject: [PATCH] Mark keys as primary with explicit method call (#7900) --- packages/core/types/src/dml/index.ts | 20 +- .../src/dml/__tests__/entity-builder.spec.ts | 171 +------------ .../src/dml/__tests__/id-property.spec.ts | 36 +-- .../src/dml/__tests__/text-property.spec.ts | 16 ++ packages/core/utils/src/dml/entity-builder.ts | 228 +++++++++--------- packages/core/utils/src/dml/entity.ts | 42 ++-- .../dml/helpers/create-mikro-orm-entity.ts | 4 +- .../infer-primary-key-properties.ts | 74 ------ packages/core/utils/src/dml/properties/id.ts | 44 ++-- .../core/utils/src/dml/properties/text.ts | 36 ++- .../src/models/notification-provider.ts | 2 +- .../notification/src/models/notification.ts | 2 +- packages/modules/region/src/models/region.ts | 2 +- 13 files changed, 239 insertions(+), 438 deletions(-) delete mode 100644 packages/core/utils/src/dml/helpers/entity-builder/infer-primary-key-properties.ts diff --git a/packages/core/types/src/dml/index.ts b/packages/core/types/src/dml/index.ts index 6236b9dd25..0414cac04b 100644 --- a/packages/core/types/src/dml/index.ts +++ b/packages/core/types/src/dml/index.ts @@ -1,10 +1,21 @@ +/** + * Symbol to identify a DML entity from an object + */ export const IsDmlEntity = Symbol.for("isDmlEntity") +/** + * Representation of DML schema. It must be a key-value pair + * with string based keys and properties/relationships + * as the value. + */ export type DMLSchema = Record< string, PropertyType | RelationshipType > +/** + * Representation of a DML entity + */ export interface IDmlEntity { [IsDmlEntity]: true schema: Schema @@ -66,9 +77,9 @@ export type PropertyType = { */ export type RelationshipOptions = { /** - * The name of the relationship as defined - * in the other data model. This is only required - * by the `belongsTo` relationship method. + * The name of the relationship as defined in the other + * data model. This is only required by the `belongsTo` + * relationship method. */ mappedBy?: string } & Record @@ -107,7 +118,8 @@ export interface EntityConstructor extends Function { } /** - * From a IDmlEntity, infer the foreign keys name and type for belongsTo relation meaning hasOne and ManyToOne + * From a IDmlEntity, infer the foreign keys name and type for + * "belongsTo" relation meaning "hasOne" and "ManyToOne" */ export type InferForeignKeys = T extends IDmlEntity ? { 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 e879890d38..db606528cf 100644 --- a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts +++ b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts @@ -1291,7 +1291,8 @@ describe("Entity builder", () => { columnType: "text", name: "id", nullable: false, - primary: true, + getter: false, + setter: false, }, username: { reference: "scalar", @@ -1346,9 +1347,9 @@ describe("Entity builder", () => { }) }) - test("mark id as non-primary", () => { + test("mark id as primary", () => { const user = model.define("user", { - id: model.id({ primaryKey: false }), + id: model.id().primaryKey(), username: model.text(), email: model.text(), }) @@ -1391,8 +1392,7 @@ describe("Entity builder", () => { columnType: "text", name: "id", nullable: false, - getter: false, - setter: false, + primary: true, }, username: { reference: "scalar", @@ -1451,7 +1451,7 @@ describe("Entity builder", () => { test("define prefix for the id", () => { const user = model.define("user", { - id: model.id({ primaryKey: false, prefix: "us" }), + id: model.id({ prefix: "us" }).primaryKey(), username: model.text(), email: model.text(), }) @@ -1494,8 +1494,7 @@ describe("Entity builder", () => { columnType: "text", name: "id", nullable: false, - getter: false, - setter: false, + primary: true, }, username: { reference: "scalar", @@ -1554,124 +1553,25 @@ describe("Entity builder", () => { }) describe("Entity builder | primaryKey", () => { - test("should create both id fields and primaryKey fields", () => { + test("should infer primaryKeys from a model", () => { const user = model.define("user", { - id: model.id(), + id: model.id().primaryKey(), email: model.text().primaryKey(), - account_id: model.number().primaryKey(), + account_id: model.number(), }) const entityBuilder = createMikrORMEntity() const User = entityBuilder(user) - - expectTypeOf(new User()).toMatchTypeOf<{ - id: string - email: string - account_id: number - }>() - const metaData = MetadataStorage.getMetadataFromDecorator(User) - expect(metaData.properties).toEqual({ - id: { - reference: "scalar", - type: "string", - columnType: "text", - name: "id", - nullable: false, - getter: false, - setter: false, - }, - email: { - columnType: "text", - name: "email", - nullable: false, - primary: true, - reference: "scalar", - type: "string", - }, - account_id: { - columnType: "integer", - name: "account_id", - nullable: false, - primary: true, - reference: "scalar", - type: "number", - }, - 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("should infer primaryKeys from a model", () => { - let user = model.define("user", { - id: model.id(), - email: model.text(), - account_id: model.number(), - }) - - const entityBuilder = createMikrORMEntity() - let User = entityBuilder(user) - let metaData = MetadataStorage.getMetadataFromDecorator(User) - expect(metaData.properties.id).toEqual({ columnType: "text", name: "id", nullable: false, + reference: "scalar", + type: "string", primary: true, - reference: "scalar", - type: "string", }) - - user = model.define("user", { - id: model.id(), - email: model.text().primaryKey(), - account_id: model.number(), - }) - - User = entityBuilder(user) - metaData = MetadataStorage.getMetadataFromDecorator(User) - - expect(metaData.properties.id).toEqual({ - columnType: "text", - name: "id", - nullable: false, - reference: "scalar", - type: "string", - getter: false, - setter: false, - }) - expect(metaData.properties.email).toEqual({ columnType: "text", name: "email", @@ -1680,53 +1580,6 @@ describe("Entity builder", () => { type: "string", primary: true, }) - - expect(metaData.properties.account_id).toEqual({ - columnType: "integer", - name: "account_id", - nullable: false, - reference: "scalar", - type: "number", - getter: false, - setter: false, - }) - - user = model.define("user", { - id: model.id(), - email: model.text().primaryKey(), - account_id: model.number().primaryKey(), - }) - - User = entityBuilder(user) - metaData = MetadataStorage.getMetadataFromDecorator(User) - - expect(metaData.properties.id).toEqual({ - columnType: "text", - name: "id", - nullable: false, - reference: "scalar", - type: "string", - getter: false, - setter: false, - }) - - expect(metaData.properties.email).toEqual({ - columnType: "text", - name: "email", - nullable: false, - reference: "scalar", - type: "string", - primary: true, - }) - - expect(metaData.properties.account_id).toEqual({ - columnType: "integer", - name: "account_id", - nullable: false, - reference: "scalar", - type: "number", - primary: true, - }) }) }) diff --git a/packages/core/utils/src/dml/__tests__/id-property.spec.ts b/packages/core/utils/src/dml/__tests__/id-property.spec.ts index c1de03f1e3..c6d190d154 100644 --- a/packages/core/utils/src/dml/__tests__/id-property.spec.ts +++ b/packages/core/utils/src/dml/__tests__/id-property.spec.ts @@ -5,24 +5,6 @@ describe("Id property", () => { test("create id property type", () => { const property = new IdProperty() - expectTypeOf(property["$dataType"]).toEqualTypeOf() - expect(property.parse("id")).toEqual({ - fieldName: "id", - dataType: { - name: "id", - options: { - primaryKey: true, - }, - }, - nullable: false, - indexes: [], - relationships: [], - }) - }) - - test("create id property type with marking it as a primary key", () => { - const property = new IdProperty({ primaryKey: false }) - expectTypeOf(property["$dataType"]).toEqualTypeOf() expect(property.parse("id")).toEqual({ fieldName: "id", @@ -37,4 +19,22 @@ describe("Id property", () => { relationships: [], }) }) + + test("create id property type with marking it as a primary key", () => { + const property = new IdProperty().primaryKey() + + expectTypeOf(property["$dataType"]).toEqualTypeOf() + expect(property.parse("id")).toEqual({ + fieldName: "id", + dataType: { + name: "id", + options: { + primaryKey: true, + }, + }, + nullable: false, + indexes: [], + relationships: [], + }) + }) }) 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 9d7e34c223..c34a28b999 100644 --- a/packages/core/utils/src/dml/__tests__/text-property.spec.ts +++ b/packages/core/utils/src/dml/__tests__/text-property.spec.ts @@ -17,4 +17,20 @@ describe("Text property", () => { relationships: [], }) }) + + test("mark text property as primary key", () => { + const property = new TextProperty().primaryKey() + + expectTypeOf(property["$dataType"]).toEqualTypeOf() + expect(property.parse("username")).toEqual({ + fieldName: "username", + dataType: { + name: "text", + options: { primaryKey: true, searchable: false }, + }, + 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 9d84a9fb2f..7b74cc9d57 100644 --- a/packages/core/utils/src/dml/entity-builder.ts +++ b/packages/core/utils/src/dml/entity-builder.ts @@ -2,7 +2,6 @@ import type { DMLSchema, RelationshipOptions } from "@medusajs/types" import { DmlEntity } from "./entity" import { createBigNumberProperties } from "./helpers/entity-builder/create-big-number-properties" import { createDefaultProperties } from "./helpers/entity-builder/create-default-properties" -import { inferPrimaryKeyProperties } from "./helpers/entity-builder/infer-primary-key-properties" import { ArrayProperty } from "./properties/array" import { BigNumberProperty } from "./properties/big-number" import { BooleanProperty } from "./properties/boolean" @@ -22,43 +21,45 @@ import { ManyToMany } from "./relations/many-to-many" */ const IMPLICIT_PROPERTIES = ["created_at", "updated_at", "deleted_at"] -export type DefineOptions = string | { - /** - * The data model's name. - */ - name?: string - /** - * The name of the data model's table in the database. - */ - tableName: string -} +export type DefineOptions = + | string + | { + /** + * The data model's name. + */ + name?: string + /** + * The name of the data model's table in the database. + */ + tableName: string + } export type ManyToManyOptions = RelationshipOptions & -( - | { - /** - * The name of the pivot table - * created in the database for this relationship. - */ - pivotTable?: string - /** - * @ignore - */ - pivotEntity?: never - } - | { - /** - * @ignore - */ - pivotTable?: never - /** - * A function that returns the data model - * representing the pivot table created in the - * database for this relationship. - */ - pivotEntity?: () => DmlEntity - } -) + ( + | { + /** + * The name of the pivot table + * created in the database for this relationship. + */ + pivotTable?: string + /** + * @ignore + */ + pivotEntity?: never + } + | { + /** + * @ignore + */ + pivotTable?: never + /** + * A function that returns the data model + * representing the pivot table created in the + * database for this relationship. + */ + pivotEntity?: () => DmlEntity + } + ) /** * Entity builder exposes the API to create an entity and define its @@ -81,21 +82,21 @@ export class EntityBuilder { /** * This method defines a data model. - * + * * @typeParam Schema - The type of the accepted schema in the second parameter of the method. - * + * * @param {DefineOptions} nameOrConfig - Either the data model's name, or configurations to name the data model. * The data model's name must be unique. * @param {Schema} schema - The schema of the data model's properties. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * id: model.id(), * name: model.text(), * }) - * + * * export default MyCustom */ define( @@ -103,7 +104,6 @@ export class EntityBuilder { schema: Schema ) { this.#disallowImplicitProperties(schema) - schema = inferPrimaryKeyProperties(schema) return new DmlEntity(nameOrConfig, { ...schema, @@ -114,40 +114,39 @@ export class EntityBuilder { /** * This method defines an automatically generated string ID property. - * - * By default, this property is considered to be the data model’s primary key. - * - * @param {ConstructorParameters[0]} options - The ID's options. - * + * + * You must use the "primaryKey" modifier to mark the property as the + * primary key. + * * @example * import { model } from "@medusajs/utils" - * - * const MyCustom = model.define("my_custom", { - * id: model.id(), + * + * const User = model.define("User", { + * id: model.id().primaryKey(), * // ... * }) - * - * export default MyCustom - * + * + * export default User + * * @customNamespace Property Types */ - id(options?: ConstructorParameters[0]) { + id(options?: { prefix?: string }) { return new IdProperty(options) } /** * This method defines a string property. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * name: model.text(), * // ... * }) - * + * * export default MyCustom - * + * * @customNamespace Property Types */ text() { @@ -156,17 +155,17 @@ export class EntityBuilder { /** * This method defines a boolean property. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * hasAccount: model.boolean(), * // ... * }) - * + * * export default MyCustom - * + * * @customNamespace Property Types */ boolean() { @@ -175,17 +174,17 @@ export class EntityBuilder { /** * This method defines a number property. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * age: model.number(), * // ... * }) - * + * * export default MyCustom - * + * * @customNamespace Property Types */ number() { @@ -194,19 +193,19 @@ export class EntityBuilder { /** * This method defines a number property that expects large numbers, such as prices. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * price: model.bigNumber(), * // ... * }) - * + * * export default MyCustom - * + * * @customNamespace Property Types - * + * * @privateRemarks * This property produces an additional * column - raw_{{ property_name }}, which stores the configuration @@ -218,17 +217,17 @@ export class EntityBuilder { /** * This method defines an array of strings property. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * names: model.array(), * // ... * }) - * + * * export default MyCustom - * + * * @customNamespace Property Types */ array() { @@ -237,17 +236,17 @@ export class EntityBuilder { /** * This method defines a timestamp property. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * date_of_birth: model.dateTime(), * // ... * }) - * + * * export default MyCustom - * + * * @customNamespace Property Types */ dateTime() { @@ -256,17 +255,17 @@ export class EntityBuilder { /** * This method defines a property whose value is a stringified JSON object. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * metadata: model.json(), * // ... * }) - * + * * export default MyCustom - * + * * @customNamespace Property Types */ json() { @@ -275,21 +274,21 @@ export class EntityBuilder { /** * This method defines a property whose value can only be one of the specified values. - * + * * @typeParam Values - The type of possible values. By default, it's `string`. - * + * * @param {Values[]} values - An array of possible values. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * color: model.enum(["black", "white"]), * // ... * }) - * + * * export default MyCustom - * + * * @customNamespace Property Types */ enum(values: Values[]) { @@ -302,24 +301,24 @@ export class EntityBuilder { * data model. * * For example: A user "hasOne" email. - * - * Use the {@link belongsTo} method to define the inverse of this relationship in + * + * Use the {@link belongsTo} method to define the inverse of this relationship in * the other data model. - * + * * @typeParam T - The type of the entity builder passed as a first parameter. By default, it's * a function returning the related model. - * + * * @param {T} entityBuilder - A function that returns the data model this model is related to. * @param {RelationshipOptions} options - The relationship's options. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const User = model.define("user", { * id: model.id(), * email: model.hasOne(() => Email), * }) - * + * * @customNamespace Relationship Methods */ hasOne(entityBuilder: T, options?: RelationshipOptions) { @@ -328,15 +327,15 @@ export class EntityBuilder { /** * This method defines the inverse of the {@link hasOne} or {@link hasMany} relationship. - * + * * For example, a product "belongsTo" a store. - * + * * @typeParam T - The type of the entity builder passed as a first parameter. By default, it's * a function returning the related model. - * + * * @param {T} entityBuilder - A function that returns the data model this model is related to. * @param {RelationshipOptions} options - The relationship's options. - * + * * @example * const Product = model.define("product", { * id: model.id(), @@ -344,7 +343,7 @@ export class EntityBuilder { * mappedBy: "products", * }), * }) - * + * * @customNamespace Relationship Methods */ belongsTo(entityBuilder: T, options?: RelationshipOptions) { @@ -357,21 +356,21 @@ export class EntityBuilder { * data model, but the related data model only has one owner. * * For example, a store "hasMany" products. - * + * * @typeParam T - The type of the entity builder passed as a first parameter. By default, it's * a function returning the related model. - * + * * @param {T} entityBuilder - A function that returns the data model this model is related to. * @param {RelationshipOptions} options - The relationship's options. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const Store = model.define("store", { * id: model.id(), * products: model.hasMany(() => Product), * }) - * + * * @customNamespace Relationship Methods */ hasMany(entityBuilder: T, options?: RelationshipOptions) { @@ -384,32 +383,29 @@ export class EntityBuilder { * * For example, an order is associated with many products, and a product * is associated with many orders. - * + * * @typeParam T - The type of the entity builder passed as a first parameter. By default, it's * a function returning the related model. - * + * * @param {T} entityBuilder - A function that returns the data model this model is related to. * @param {RelationshipOptions} options - The relationship's options. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const Order = model.define("order", { * id: model.id(), * products: model.manyToMany(() => Product), * }) - * + * * const Product = model.define("product", { * id: model.id(), * order: model.manyToMany(() => Order), * }) - * + * * @customNamespace Relationship Methods */ - manyToMany( - entityBuilder: T, - options?: ManyToManyOptions - ) { + manyToMany(entityBuilder: T, options?: ManyToManyOptions) { return new ManyToMany(entityBuilder, options || {}) } } diff --git a/packages/core/utils/src/dml/entity.ts b/packages/core/utils/src/dml/entity.ts index fe55441195..b56ca1a8b9 100644 --- a/packages/core/utils/src/dml/entity.ts +++ b/packages/core/utils/src/dml/entity.ts @@ -96,16 +96,16 @@ export class DmlEntity implements IDmlEntity { /** * This method configures which related data models an operation, such as deletion, * should be cascaded to. - * + * * For example, if a store is deleted, its product should also be deleted. - * + * * @param options - The cascades configurations. They object's keys are the names of - * the actions, such as `deleted`, and the value is an array of relations that the + * the actions, such as `deleted`, and the value is an array of relations that the * action should be cascaded to. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const Store = model.define("store", { * id: model.id(), * products: model.hasMany(() => Product), @@ -113,7 +113,7 @@ export class DmlEntity implements IDmlEntity { * .cascades({ * delete: ["products"], * }) - * + * * @customNamespace Model Methods */ cascades( @@ -142,15 +142,15 @@ export class DmlEntity implements IDmlEntity { /** * This method defines indices on the data model. An index can be on multiple columns * and have conditions. - * + * * @param indexes - The index's configuration. - * + * * @example * An example of a simple index: - * + * * ```ts * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * id: model.id(), * name: model.text(), @@ -160,15 +160,15 @@ export class DmlEntity implements IDmlEntity { * on: ["name", "age"], * }, * ]) - * + * * export default MyCustom * ``` - * + * * To add a condition on the index, use the `where` option: - * + * * ```ts * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * id: model.id(), * name: model.text(), @@ -181,15 +181,15 @@ export class DmlEntity implements IDmlEntity { * } * }, * ]) - * + * * export default MyCustom * ``` - * + * * The condition can also be a negation. For example: - * + * * ```ts * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * id: model.id(), * name: model.text(), @@ -204,12 +204,12 @@ export class DmlEntity implements IDmlEntity { * } * }, * ]) - * + * * export default MyCustom * ``` - * + * * In this example, the index is created when the value of `age` doesn't equal `30`. - * + * * @customNamespace Model Methods */ indexes(indexes: EntityIndex>[]) { 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 84d47a25eb..104984d88f 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 @@ -87,14 +87,14 @@ export function createMikrORMEntity() { */ export const toMikroORMEntity = ( entity: T -): T extends DmlEntity ? Infer : T => { +): T extends DmlEntity ? Infer : T => { let mikroOrmEntity: T | EntityConstructor = entity if (DmlEntity.isDmlEntity(entity)) { mikroOrmEntity = createMikrORMEntity()(entity) } - return mikroOrmEntity as T extends DmlEntity ? Infer : T + return mikroOrmEntity as T extends DmlEntity ? Infer : T } /** diff --git a/packages/core/utils/src/dml/helpers/entity-builder/infer-primary-key-properties.ts b/packages/core/utils/src/dml/helpers/entity-builder/infer-primary-key-properties.ts deleted file mode 100644 index 2db9db22f3..0000000000 --- a/packages/core/utils/src/dml/helpers/entity-builder/infer-primary-key-properties.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { DMLSchema } from "@medusajs/types" -import { IdProperty } from "../../properties/id" - -/* - The id() property is an core opinionated property that will act as a primaryKey - by default and come with built-in logic when converted to a mikroorm entity. If no other - primaryKey() properties are found within the schema, we continue treating the id() property - as a primaryKey. When other fields are set as explicit primaryKey fields, we convert the - id() property to no longer be a primaryKey. - - Example: - Model 1: - id: model.id() -> primary key - code: model.text() - - Model 2: - id: model.id() - code: model.text().primaryKey() -> primary key - - Model 3: - id: model.id() - code: model.text().primaryKey() -> composite primary key - name: model.text().primaryKey() -> composite primary key -*/ -export function inferPrimaryKeyProperties( - schema: TSchema -) { - // If explicit primaryKey fields are not found, no inferrence is required. Return early. - if (!getExplicitPrimaryKeyFields(schema).length) { - return schema - } - - // If explicit primaryKey fields are found, set any id() properties to no longer be - // set to primaryKey. - for (const [field, property] of Object.entries(schema)) { - const parsed = property.parse(field) - const isRelationshipType = "type" in parsed - - if (isRelationshipType) { - continue - } - - if (parsed.dataType.name === "id") { - ;(property as IdProperty).primaryKey(false) - } - } - - return schema -} - -/* - Gets all explicit primary key fields from a schema, except id properties. - - eg: model.define('test', { - id: model.id(), -> implicit primaryKey field, - text: model.text(), - textPrimary: model.text().primaryKey(), -> explicit primaryKey field - numberPrimary: model.number().primaryKey(), -> explicit primaryKey field - belongsTo: model.belongsTo(() => belongsToAnother), - }) -*/ -function getExplicitPrimaryKeyFields(schema: DMLSchema) { - return Object.entries(schema).filter(([field, property]) => { - const parsed = property.parse(field) - const isRelationshipType = "type" in parsed - - // Return early if its a relationship property or an id property - if (isRelationshipType || parsed.dataType.name === "id") { - return false - } - - return !!parsed.dataType.options?.primaryKey - }) -} diff --git a/packages/core/utils/src/dml/properties/id.ts b/packages/core/utils/src/dml/properties/id.ts index 17fd670e81..42d5325b9f 100644 --- a/packages/core/utils/src/dml/properties/id.ts +++ b/packages/core/utils/src/dml/properties/id.ts @@ -11,33 +11,33 @@ export class IdProperty extends BaseProperty { primaryKey: boolean prefix?: string } + } = { + name: "id", + options: { primaryKey: false }, } - constructor(options?: { - /** - * Whether the ID is the data model's primary key. - * - * @defaultValue true - */ - primaryKey?: boolean - /** - * By default, Medusa shortens the data model's name and uses it as the - * prefix of all IDs. For example, `cm_123`. - * - * Use this option to specify the prefix to use instead. - */ - prefix?: string - }) { + constructor(options?: { prefix?: string }) { super() - this.dataType = { - name: "id", - options: { primaryKey: true, ...options }, - } + this.dataType.options.prefix = options?.prefix } - primaryKey(decision: boolean) { - this.dataType.options.primaryKey = decision - + /** + * This method indicates that the property is the data model's primary key. + * + * @example + * import { model } from "@medusajs/utils" + * + * const Product = model.define("Product", { + * id: model.id().primaryKey(), + * // ... + * }) + * + * export default Product + * + * @customNamespace Property Configuration Methods + */ + primaryKey() { + this.dataType.options.primaryKey = true return this } } diff --git a/packages/core/utils/src/dml/properties/text.ts b/packages/core/utils/src/dml/properties/text.ts index 68c730df38..052c32b4c2 100644 --- a/packages/core/utils/src/dml/properties/text.ts +++ b/packages/core/utils/src/dml/properties/text.ts @@ -8,23 +8,30 @@ export class TextProperty extends BaseProperty { name: "text" options: { primaryKey: boolean + prefix?: string searchable: boolean } + } = { + name: "text", + options: { + primaryKey: false, + searchable: false, + }, } /** * This method indicates that the property is the data model's primary key. - * + * * @example * import { model } from "@medusajs/utils" - * - * const MyCustom = model.define("my_custom", { + * + * const Product = model.define("Product", { * code: model.text().primaryKey(), * // ... * }) - * - * export default MyCustom - * + * + * export default Product + * * @customNamespace Property Configuration Methods */ primaryKey() { @@ -35,17 +42,17 @@ export class TextProperty extends BaseProperty { /** * This method indicates that a text property is searchable. - * + * * @example * import { model } from "@medusajs/utils" - * + * * const MyCustom = model.define("my_custom", { * name: model.text().searchable(), * // ... * }) - * + * * export default MyCustom - * + * * @customNamespace Property Configuration Methods */ searchable() { @@ -53,13 +60,4 @@ export class TextProperty extends BaseProperty { return this } - - constructor(options?: { primaryKey?: boolean; searchable?: boolean }) { - super() - - this.dataType = { - name: "text", - options: { primaryKey: false, searchable: false, ...options }, - } - } } diff --git a/packages/modules/notification/src/models/notification-provider.ts b/packages/modules/notification/src/models/notification-provider.ts index bb2d6e6a33..e5548308e9 100644 --- a/packages/modules/notification/src/models/notification-provider.ts +++ b/packages/modules/notification/src/models/notification-provider.ts @@ -2,7 +2,7 @@ import { model } from "@medusajs/utils" import { Notification } from "./notification" export const NotificationProvider = model.define("notificationProvider", { - id: model.id({ prefix: "notpro" }), + id: model.id({ prefix: "notpro" }).primaryKey(), handle: model.text(), name: model.text(), is_enabled: model.boolean().default(true), diff --git a/packages/modules/notification/src/models/notification.ts b/packages/modules/notification/src/models/notification.ts index 29a97f66a9..2b214974d6 100644 --- a/packages/modules/notification/src/models/notification.ts +++ b/packages/modules/notification/src/models/notification.ts @@ -3,7 +3,7 @@ import { NotificationProvider } from "./notification-provider" // We probably want to have a TTL for each entry, so we don't bloat the DB (and also for GDPR reasons if TTL < 30 days). export const Notification = model.define("notification", { - id: model.id({ prefix: "noti" }), + id: model.id({ prefix: "noti" }).primaryKey(), // This can be an email, phone number, or username, depending on the channel. to: model.text(), channel: model.text(), diff --git a/packages/modules/region/src/models/region.ts b/packages/modules/region/src/models/region.ts index dabef95524..014c5b0a13 100644 --- a/packages/modules/region/src/models/region.ts +++ b/packages/modules/region/src/models/region.ts @@ -2,7 +2,7 @@ import { model } from "@medusajs/utils" import RegionCountry from "./country" const Region = model.define("region", { - id: model.id({ prefix: "reg" }), + id: model.id({ prefix: "reg" }).primaryKey(), name: model.text().searchable(), currency_code: model.text().searchable(), automatic_taxes: model.boolean().default(true),