diff --git a/integration-tests/http/__tests__/currency/admin/currency.spec.ts b/integration-tests/http/__tests__/currency/admin/currency.spec.ts index d694d83a06..2edd087764 100644 --- a/integration-tests/http/__tests__/currency/admin/currency.spec.ts +++ b/integration-tests/http/__tests__/currency/admin/currency.spec.ts @@ -1,7 +1,7 @@ import { medusaIntegrationTestRunner } from "medusa-test-utils" import { - createAdminUser, adminHeaders, + createAdminUser, } from "../../../../helpers/create-admin-user" jest.setTimeout(30000) @@ -40,24 +40,26 @@ medusaIntegrationTestRunner({ ) expect(response.status).toEqual(200) - expect(response.data.currencies).toEqual([ - expect.objectContaining({ - code: "aud", - name: "Australian Dollar", - }), - expect.objectContaining({ - code: "byn", - name: "Belarusian Ruble", - }), - expect.objectContaining({ - code: "rub", - name: "Russian Ruble", - }), - expect.objectContaining({ - code: "usd", - name: "US Dollar", - }), - ]) + expect(response.data.currencies).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: "aud", + name: "Australian Dollar", + }), + expect.objectContaining({ + code: "byn", + name: "Belarusian Ruble", + }), + expect.objectContaining({ + code: "rub", + name: "Russian Ruble", + }), + expect.objectContaining({ + code: "usd", + name: "US Dollar", + }), + ]) + ) }) }) }, diff --git a/integration-tests/modules/__tests__/currency/admin/currency.spec.ts b/integration-tests/modules/__tests__/currency/admin/currency.spec.ts index 5783fe0640..aab4b3fd55 100644 --- a/integration-tests/modules/__tests__/currency/admin/currency.spec.ts +++ b/integration-tests/modules/__tests__/currency/admin/currency.spec.ts @@ -60,9 +60,10 @@ medusaIntegrationTestRunner({ adminHeaders ) - expect(listResp.data.currencies).toHaveLength(1) - expect(listResp.data.currencies[0]).toEqual( - expect.objectContaining({ code: "zwl", name: "Zimbabwean Dollar" }) + expect(listResp.data.currencies).toEqual( + expect.arrayContaining([ + expect.objectContaining({ code: "zwl", name: "Zimbabwean Dollar" }), + ]) ) }) }) 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 394ca91b8a..c98d12dd5d 100644 --- a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts +++ b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts @@ -1166,6 +1166,109 @@ describe("Entity builder", () => { }) }) + describe("Entity builder | primaryKey", () => { + test("should create both id fields and primaryKey fields", () => { + const model = new EntityBuilder() + const user = model.define("user", { + id: model.id({ primaryKey: false }), + email: model.text().primaryKey(), + account_id: model.number().primaryKey(), + }) + + const entityBuilder = createMikrORMEntity() + const User = entityBuilder(user) + + expectTypeOf(new User()).toMatchTypeOf<{ + id: string + email: string + account_id: number + }>() + + const metaData = MetadataStorage.getMetadataFromDecorator(User) + const userInstance = new User() + userInstance["generateId"]() + + expect(metaData.className).toEqual("User") + expect(metaData.path).toEqual("User") + + expect(metaData.hooks).toEqual({ + beforeCreate: ["generateId"], + onInit: ["generateId"], + }) + + expect(metaData.filters).toEqual({ + softDeletable: { + name: "softDeletable", + cond: expect.any(Function), + default: true, + args: false, + }, + }) + + 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, + }, + }) + + expect(userInstance.id).toBeDefined() + }) + }) + describe("Entity builder | indexes", () => { test("define index on a field", () => { const model = new EntityBuilder() diff --git a/packages/core/utils/src/dml/__tests__/number-property.spec.ts b/packages/core/utils/src/dml/__tests__/number-property.spec.ts index 04d4b17b2c..df2d7f987c 100644 --- a/packages/core/utils/src/dml/__tests__/number-property.spec.ts +++ b/packages/core/utils/src/dml/__tests__/number-property.spec.ts @@ -10,6 +10,9 @@ describe("Number property", () => { fieldName: "age", dataType: { name: "number", + options: { + primaryKey: false, + }, }, nullable: false, indexes: [], 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 e90223a133..98d792621e 100644 --- a/packages/core/utils/src/dml/__tests__/text-property.spec.ts +++ b/packages/core/utils/src/dml/__tests__/text-property.spec.ts @@ -10,6 +10,7 @@ describe("Text property", () => { fieldName: "username", dataType: { name: "text", + options: { primaryKey: false }, }, nullable: false, indexes: [], 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 55cea2f929..f96eb0df7b 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 @@ -240,7 +240,10 @@ export function createMikrORMEntity() { * Hook to generate entity within the code */ MikroORMEntity.prototype.generateId = function () { - this.id = generateEntityId(this.id, field.dataType.options?.prefix) + this[field.fieldName] = generateEntityId( + this[field.fieldName], + field.dataType.options?.prefix + ) } /** @@ -248,6 +251,7 @@ export function createMikrORMEntity() { */ BeforeCreate()(MikroORMEntity.prototype, "generateId") OnInit()(MikroORMEntity.prototype, "generateId") + return } @@ -257,6 +261,19 @@ export function createMikrORMEntity() { const columnType = COLUMN_TYPES[field.dataType.name] const propertyType = PROPERTY_TYPES[field.dataType.name] + /** + * Defining a primary key property + */ + if (field.dataType.options?.primaryKey) { + PrimaryKey({ + columnType, + type: propertyType, + nullable: false, + })(MikroORMEntity.prototype, field.fieldName) + + return + } + Property({ columnType, type: propertyType, diff --git a/packages/core/utils/src/dml/properties/id.ts b/packages/core/utils/src/dml/properties/id.ts index b1e75996d5..86b4ca8c68 100644 --- a/packages/core/utils/src/dml/properties/id.ts +++ b/packages/core/utils/src/dml/properties/id.ts @@ -15,6 +15,9 @@ export class IdProperty extends BaseProperty { constructor(options?: { primaryKey?: boolean; prefix?: string }) { super() - this.dataType = { name: "id", options: { primaryKey: true, ...options } } + this.dataType = { + name: "id", + options: { primaryKey: true, ...options }, + } } } diff --git a/packages/core/utils/src/dml/properties/number.ts b/packages/core/utils/src/dml/properties/number.ts index cf3d2ea73f..e72d0cccdc 100644 --- a/packages/core/utils/src/dml/properties/number.ts +++ b/packages/core/utils/src/dml/properties/number.ts @@ -5,7 +5,25 @@ import { BaseProperty } from "./base" * property */ export class NumberProperty extends BaseProperty { - protected dataType = { - name: "number", - } as const + protected dataType: { + name: "number" + options: { + primaryKey: boolean + } + } + + primaryKey() { + this.dataType.options.primaryKey = true + + return this + } + + constructor(options?: { primaryKey?: boolean }) { + super() + + this.dataType = { + name: "number", + options: { primaryKey: false, ...options }, + } + } } diff --git a/packages/core/utils/src/dml/properties/text.ts b/packages/core/utils/src/dml/properties/text.ts index 1728336b48..7a06be262c 100644 --- a/packages/core/utils/src/dml/properties/text.ts +++ b/packages/core/utils/src/dml/properties/text.ts @@ -4,7 +4,25 @@ import { BaseProperty } from "./base" * The TextProperty is used to define a textual property */ export class TextProperty extends BaseProperty { - protected dataType = { - name: "text", - } as const + protected dataType: { + name: "text" + options: { + primaryKey: boolean + } + } + + primaryKey() { + this.dataType.options.primaryKey = true + + return this + } + + constructor(options?: { primaryKey?: boolean }) { + super() + + this.dataType = { + name: "text", + options: { primaryKey: false, ...options }, + } + } } diff --git a/packages/core/utils/src/modules-sdk/__tests__/joiner-config-builder.spec.ts b/packages/core/utils/src/modules-sdk/__tests__/joiner-config-builder.spec.ts index af42bf7d84..24d3958158 100644 --- a/packages/core/utils/src/modules-sdk/__tests__/joiner-config-builder.spec.ts +++ b/packages/core/utils/src/modules-sdk/__tests__/joiner-config-builder.spec.ts @@ -279,4 +279,37 @@ describe("defineJoiner", () => { ], }) }) + + it("should return a full joiner configuration with custom aliases overriding defaults", () => { + const joinerConfig = defineJoinerConfig(Modules.FULFILLMENT, { + entityQueryingConfig: [FulfillmentSet], + alias: [ + { + name: ["fulfillment_set", "fulfillment_sets"], + args: { + entity: "FulfillmentSet", + methodSuffix: "fulfillmentSetCustom", + }, + }, + ], + }) + + expect(joinerConfig).toEqual({ + serviceName: Modules.FULFILLMENT, + primaryKeys: ["id"], + schema: undefined, + linkableKeys: { + fulfillment_set_id: FulfillmentSet.name, + }, + alias: [ + { + name: ["fulfillment_set", "fulfillment_sets"], + args: { + entity: "FulfillmentSet", + methodSuffix: "fulfillmentSetCustom", + }, + }, + ], + }) + }) }) diff --git a/packages/core/utils/src/modules-sdk/index.ts b/packages/core/utils/src/modules-sdk/index.ts index 889c87cea6..d44eaabeaa 100644 --- a/packages/core/utils/src/modules-sdk/index.ts +++ b/packages/core/utils/src/modules-sdk/index.ts @@ -1,15 +1,15 @@ -export * from "./load-module-database-config" -export * from "./decorators" export * from "./build-query" -export * from "./loaders/mikro-orm-connection-loader" -export * from "./loaders/mikro-orm-connection-loader-factory" -export * from "./loaders/container-loader-factory" export * from "./create-pg-connection" -export * from "./migration-scripts" -export * from "./medusa-internal-service" -export * from "./medusa-service" +export * from "./decorators" export * from "./definition" export * from "./event-builder-factory" export * from "./joiner-config-builder" +export * from "./load-module-database-config" +export * from "./loaders/container-loader-factory" export * from "./loaders/load-models" +export * from "./loaders/mikro-orm-connection-loader" +export * from "./loaders/mikro-orm-connection-loader-factory" +export * from "./medusa-internal-service" +export * from "./medusa-service" +export * from "./migration-scripts" export * from "./mikro-orm-cli-config-builder" diff --git a/packages/core/utils/src/modules-sdk/joiner-config-builder.ts b/packages/core/utils/src/modules-sdk/joiner-config-builder.ts index 456735ce89..aa7e17139d 100644 --- a/packages/core/utils/src/modules-sdk/joiner-config-builder.ts +++ b/packages/core/utils/src/modules-sdk/joiner-config-builder.ts @@ -1,4 +1,5 @@ -import { ModuleJoinerConfig } from "@medusajs/types" +import { JoinerServiceConfigAlias, ModuleJoinerConfig } from "@medusajs/types" +import { join } from "path" import { camelToSnakeCase, deduplicate, @@ -7,7 +8,6 @@ import { pluralize, upperCaseFirst, } from "../common" -import { join } from "path" import { loadModels } from "./loaders/load-models" /** @@ -33,7 +33,7 @@ export function defineJoinerConfig( linkableKeys, primaryKeys, }: { - alias?: ModuleJoinerConfig["alias"] + alias?: JoinerServiceConfigAlias[] schema?: string entityQueryingConfig?: { name: string }[] linkableKeys?: Record @@ -79,16 +79,22 @@ export function defineJoinerConfig( pluralize(upperCaseFirst(alias.args.entity)), }, })), - ...models.map((entity, i) => ({ - name: [ - `${camelToSnakeCase(entity.name).toLowerCase()}`, - `${pluralize(camelToSnakeCase(entity.name).toLowerCase())}`, - ], - args: { - entity: entity.name, - methodSuffix: pluralize(upperCaseFirst(entity.name)), - }, - })), + ...models + .filter((model) => { + return ( + !alias || !alias.some((alias) => alias.args?.entity === model.name) + ) + }) + .map((entity, i) => ({ + name: [ + `${camelToSnakeCase(entity.name).toLowerCase()}`, + `${pluralize(camelToSnakeCase(entity.name).toLowerCase())}`, + ], + args: { + entity: entity.name, + methodSuffix: pluralize(upperCaseFirst(entity.name)), + }, + })), ], } } diff --git a/packages/core/utils/src/modules-sdk/loaders/load-models.ts b/packages/core/utils/src/modules-sdk/loaders/load-models.ts index 60f60571b1..8026e8bc10 100644 --- a/packages/core/utils/src/modules-sdk/loaders/load-models.ts +++ b/packages/core/utils/src/modules-sdk/loaders/load-models.ts @@ -27,10 +27,11 @@ export function loadModels(basePath: string) { if (stats.isFile()) { try { - const required = require(filePath) - return Object.values(required).filter( - (resource) => typeof resource === "function" && !!resource.name - ) + const required = require(filePath) as { + [key: string]: { name?: string } + } + + return Object.values(required).filter((resource) => !!resource.name) } catch (e) {} } diff --git a/packages/core/utils/src/modules-sdk/mikro-orm-cli-config-builder.ts b/packages/core/utils/src/modules-sdk/mikro-orm-cli-config-builder.ts index 1f593a7362..4d3a835a27 100644 --- a/packages/core/utils/src/modules-sdk/mikro-orm-cli-config-builder.ts +++ b/packages/core/utils/src/modules-sdk/mikro-orm-cli-config-builder.ts @@ -1,5 +1,4 @@ import { MikroORMOptions } from "@mikro-orm/core/utils/Configuration" -import { IDmlEntity } from "@medusajs/types" import { DmlEntity, toMikroORMEntity } from "../dml" import { TSMigrationGenerator } from "../dal" import { AnyEntity, EntityClassGroup, EntitySchema } from "@mikro-orm/core" @@ -11,7 +10,7 @@ type Options = Partial & { | EntityClass | EntityClassGroup | EntitySchema - | IDmlEntity + | DmlEntity )[] databaseName: string } diff --git a/packages/modules/currency/mikro-orm.config.dev.ts b/packages/modules/currency/mikro-orm.config.dev.ts index 4383273e07..37e66a6be2 100644 --- a/packages/modules/currency/mikro-orm.config.dev.ts +++ b/packages/modules/currency/mikro-orm.config.dev.ts @@ -1,8 +1,7 @@ +import { defineMikroOrmCliConfig } from "@medusajs/utils" import * as entities from "./src/models" -module.exports = { +export default defineMikroOrmCliConfig({ entities: Object.values(entities), - schema: "public", - clientUrl: "postgres://postgres@localhost/medusa-currency", - type: "postgresql", -} + databaseName: "medusa-currency", +}) diff --git a/packages/modules/currency/src/joiner-config.ts b/packages/modules/currency/src/joiner-config.ts index 06bdffed3d..21da320a91 100644 --- a/packages/modules/currency/src/joiner-config.ts +++ b/packages/modules/currency/src/joiner-config.ts @@ -5,7 +5,12 @@ import { Modules, } from "@medusajs/utils" -export const joinerConfig = defineJoinerConfig(Modules.CURRENCY) +export const joinerConfig = defineJoinerConfig(Modules.CURRENCY, { + primaryKeys: ["code"], + linkableKeys: { + currency_code: "currency", + }, +}) export const entityNameToLinkableKeysMap: MapToConfig = buildEntitiesNameToLinkableKeysMap(joinerConfig.linkableKeys) diff --git a/packages/modules/currency/src/loaders/initial-data.ts b/packages/modules/currency/src/loaders/initial-data.ts index 14f05dcf39..8da5115117 100644 --- a/packages/modules/currency/src/loaders/initial-data.ts +++ b/packages/modules/currency/src/loaders/initial-data.ts @@ -14,7 +14,7 @@ export default async ({ const logger = container.resolve(ContainerRegistrationKeys.LOGGER) ?? console const { currencyService_ } = container.resolve<{ - currencyService_: ModulesSdkTypes.IMedusaInternalService + currencyService_: ModulesSdkTypes.IMedusaInternalService }>(ModuleRegistrationName.CURRENCY) try { diff --git a/packages/modules/currency/src/migrations/.snapshot-medusa-currency.json b/packages/modules/currency/src/migrations/.snapshot-medusa-currency.json index 296f2b87b2..d090bff0d9 100644 --- a/packages/modules/currency/src/migrations/.snapshot-medusa-currency.json +++ b/packages/modules/currency/src/migrations/.snapshot-medusa-currency.json @@ -42,7 +42,7 @@ }, "decimal_digits": { "name": "decimal_digits", - "type": "int", + "type": "integer", "unsigned": false, "autoincrement": false, "primary": false, @@ -90,6 +90,16 @@ "length": 6, "default": "now()", "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" } }, "name": "currency", diff --git a/packages/modules/currency/src/migrations/Migration20240624082354.ts b/packages/modules/currency/src/migrations/Migration20240624082354.ts new file mode 100644 index 0000000000..73e13c0528 --- /dev/null +++ b/packages/modules/currency/src/migrations/Migration20240624082354.ts @@ -0,0 +1,15 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20240624082354 extends Migration { + async up(): Promise { + this.addSql( + 'alter table if exists "currency" add column if not exists "deleted_at" timestamptz null;' + ) + } + + async down(): Promise { + this.addSql( + 'alter table if exists "currency" drop column if exists "deleted_at";' + ) + } +} diff --git a/packages/modules/currency/src/models/currency.ts b/packages/modules/currency/src/models/currency.ts index 0ba5d969f2..25e3b309e9 100644 --- a/packages/modules/currency/src/models/currency.ts +++ b/packages/modules/currency/src/models/currency.ts @@ -1,50 +1,10 @@ -import { BigNumberRawValue } from "@medusajs/types" -import { - BigNumber, - MikroOrmBigNumberProperty, - Searchable, -} from "@medusajs/utils" -import { Entity, PrimaryKey, Property } from "@mikro-orm/core" +import { model } from "@medusajs/utils" -@Entity({ tableName: "currency" }) -class Currency { - @Searchable() - @PrimaryKey({ columnType: "text" }) - code!: string - - @Property({ columnType: "text" }) - symbol: string - - @Property({ columnType: "text" }) - symbol_native: string - - @Searchable() - @Property({ columnType: "text" }) - name: string - - @Property({ columnType: "int", default: 0 }) - decimal_digits: number - - @MikroOrmBigNumberProperty({ default: 0 }) - rounding: BigNumber | number - - @Property({ columnType: "jsonb" }) - raw_rounding: BigNumberRawValue - - @Property({ - onCreate: () => new Date(), - columnType: "timestamptz", - defaultRaw: "now()", - }) - created_at: Date - - @Property({ - onCreate: () => new Date(), - onUpdate: () => new Date(), - columnType: "timestamptz", - defaultRaw: "now()", - }) - updated_at: Date -} - -export default Currency +export default model.define("currency", { + code: model.text().primaryKey(), + symbol: model.text(), + symbol_native: model.text(), + name: model.text(), + decimal_digits: model.number().default(0), + rounding: model.bigNumber().default(0), +}) diff --git a/packages/modules/currency/src/services/currency-module-service.ts b/packages/modules/currency/src/services/currency-module-service.ts index 19f931f4d2..2d7e3ee579 100644 --- a/packages/modules/currency/src/services/currency-module-service.ts +++ b/packages/modules/currency/src/services/currency-module-service.ts @@ -11,9 +11,9 @@ import { ModulesSdkTypes, } from "@medusajs/types" +import { MedusaService } from "@medusajs/utils" import { Currency } from "@models" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" -import { MedusaService } from "@medusajs/utils" type InjectedDependencies = { baseRepository: DAL.RepositoryService @@ -27,7 +27,9 @@ export default class CurrencyModuleService implements ICurrencyModuleService { protected baseRepository_: DAL.RepositoryService - protected readonly currencyService_: ModulesSdkTypes.IMedusaInternalService + protected readonly currencyService_: ModulesSdkTypes.IMedusaInternalService< + typeof Currency + > constructor( { baseRepository, currencyService }: InjectedDependencies,