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 6608610555..1d24141521 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 @@ -458,45 +458,61 @@ describe("joiner-config-builder", () => { number_plate: model.text().primaryKey(), }) - const linkConfig = buildLinkConfigFromDmlObjects([user, car]) + const linkConfig = buildLinkConfigFromDmlObjects("myService", [user, car]) expectTypeOf(linkConfig).toMatchTypeOf<{ user: { id: { + serviceName: "myService" + field: "user" linkable: "user_id" primaryKey: "id" } toJSON: () => { - linkable: string - primaryKey: string + serviceName: "myService" + field: "user" + linkable: "user_id" + primaryKey: "id" } } car: { number_plate: { + serviceName: "myService" + field: "car" linkable: "car_number_plate" primaryKey: "number_plate" } toJSON: () => { - linkable: string - primaryKey: string + serviceName: "myService" + field: "car" + linkable: "car_number_plate" + primaryKey: "number_plate" } } }>() expect(linkConfig.user.id).toEqual({ + serviceName: "myService", + field: "user", linkable: "user_id", primaryKey: "id", }) expect(linkConfig.car.number_plate).toEqual({ + serviceName: "myService", + field: "car", linkable: "car_number_plate", primaryKey: "number_plate", }) expect(linkConfig.car.toJSON()).toEqual({ + serviceName: "myService", + field: "car", linkable: "car_number_plate", primaryKey: "number_plate", }) expect(linkConfig.user.toJSON()).toEqual({ + serviceName: "myService", + field: "user", linkable: "user_id", primaryKey: "id", }) 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 273efefdca..591c8005cb 100644 --- a/packages/core/utils/src/modules-sdk/joiner-config-builder.ts +++ b/packages/core/utils/src/modules-sdk/joiner-config-builder.ts @@ -27,7 +27,7 @@ import { InferLinkableKeys, InfersLinksConfig } from "./types/links-config" * The aliases will be built from the entityQueryingConfig and custom aliases if provided, in case of aliases provided if the methodSuffix is not provided * then it will be inferred from the entity name of the alias args. * - * @param moduleName + * @param serviceName * @param alias * @param schema * @param models @@ -35,7 +35,7 @@ import { InferLinkableKeys, InfersLinksConfig } from "./types/links-config" * @param primaryKeys */ export function defineJoinerConfig( - moduleName: string, + serviceName: string, { alias, schema, @@ -108,7 +108,7 @@ export function defineJoinerConfig( } if (!primaryKeys && modelDefinitions.size) { - const linkConfig = buildLinkConfigFromDmlObjects([ + const linkConfig = buildLinkConfigFromDmlObjects(serviceName, [ ...modelDefinitions.values(), ]) @@ -126,7 +126,7 @@ export function defineJoinerConfig( // TODO: In the context of DML add a validation on primary keys and linkable keys if the consumer provide them manually. follow up pr return { - serviceName: moduleName, + serviceName, primaryKeys: primaryKeys ?? ["id"], schema, linkableKeys: linkableKeys, @@ -250,23 +250,41 @@ export function buildLinkableKeysFromMikroOrmObjects( * test: model.text(), * }) * + * const links = buildLinkConfigFromDmlObjects('userService', [user, car]) + * * // output: * // { - * // toJSON: function () { }, * // user: { - * // id: "user_id", + * // id: { + * // serviceName: 'userService', + * // field: 'user', + * // linkable: 'user_id', + * // primaryKey: 'id' + * // }, + * // toJSON() { ... } * // }, * // car: { - * // number_plate: "car_number_plate", - * // }, + * // number_plate: { + * // serviceName: 'userService', + * // field: 'car', + * // linkable: 'car_number_plate', + * // primaryKey: 'number_plate' + * // }, + * // toJSON() { ... } + * // } * // } * + * @param serviceName * @param models */ export function buildLinkConfigFromDmlObjects< + const ServiceName extends string, const T extends DmlEntity[] ->(models: T = [] as unknown as T): InfersLinksConfig { - const linkConfig = {} as InfersLinksConfig +>( + serviceName: ServiceName, + models: T = [] as unknown as T +): InfersLinksConfig { + const linkConfig = {} as InfersLinksConfig for (const model of models) { if (!DmlEntity.isDmlEntity(model)) { @@ -297,12 +315,14 @@ export function buildLinkConfigFromDmlObjects< modelLinkConfig[property] = { linkable: linkableKeyName, primaryKey: property, + serviceName, + field: lowerCaseFirst(model.name), } } } } - return linkConfig as InfersLinksConfig & Record + return linkConfig as InfersLinksConfig & Record } /** diff --git a/packages/core/utils/src/modules-sdk/module.ts b/packages/core/utils/src/modules-sdk/module.ts index c4ae408724..444ce7c697 100644 --- a/packages/core/utils/src/modules-sdk/module.ts +++ b/packages/core/utils/src/modules-sdk/module.ts @@ -10,12 +10,14 @@ import { DmlEntity } from "../dml" /** * Wrapper to build the module export and auto generate the joiner config if needed as well as * return a links object based on the DML objects - * @param moduleName + * + * @param serviceName * @param service * @param loaders * @constructor */ export function Module< + const ServiceName extends string, const Service extends Constructor, const ModelObjects extends DmlEntity[] = Service extends { $modelObjects: infer $DmlObjects @@ -24,22 +26,21 @@ export function Module< : [], Links = keyof ModelObjects extends never ? Record - : InfersLinksConfig ->({ - name = "", - service, - loaders, -}: ModuleExports & { name?: string }): ModuleExports & { + : InfersLinksConfig +>( + serviceName: ServiceName, + { service, loaders }: ModuleExports +): ModuleExports & { links: Links } { - service.prototype.__joinerConfig ??= defineJoinerConfig(name) + service.prototype.__joinerConfig ??= defineJoinerConfig(serviceName) const dmlObjects = service[MedusaServiceModelObjectsSymbol] return { service, loaders, links: (dmlObjects?.length - ? buildLinkConfigFromDmlObjects(dmlObjects) + ? buildLinkConfigFromDmlObjects(dmlObjects) : {}) as Links, } } diff --git a/packages/core/utils/src/modules-sdk/types/links-config.ts b/packages/core/utils/src/modules-sdk/types/links-config.ts index becbaeea0d..c0391caa73 100644 --- a/packages/core/utils/src/modules-sdk/types/links-config.ts +++ b/packages/core/utils/src/modules-sdk/types/links-config.ts @@ -7,6 +7,10 @@ import { import { DmlEntity } from "../../dml" import { PrimaryKeyModifier } from "../../dml/properties/primary-key" +/** + * Utils + */ + type FlattenUnion = T extends { [K in keyof T]: infer U } ? { [K in keyof T]: U } : never @@ -17,6 +21,30 @@ type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( ? I : never +type UnionToOvlds = UnionToIntersection< + U extends any ? (f: U) => void : never +> + +type PopUnion = UnionToOvlds extends (a: infer A) => void ? A : never + +type IsUnion = [T] extends [UnionToIntersection] ? false : true + +type UnionToArray = IsUnion extends true + ? UnionToArray>, [PopUnion, ...A]> + : [T, ...A] + +type Reverse = ReturnType< + T extends [infer F, ...infer L] ? () => Reverse : () => R +> + +/** + * End of utils + */ + +/** + * Linkable keys + */ + type InferLinkableKeyName< Key, Property, @@ -67,16 +95,61 @@ type AggregateSchemasLinkableKeys[]> = { export type InferLinkableKeys[]> = UnionToIntersection>[0]> +/** + * End Linkable keys + */ + +/** + * Links config + */ + +/** + * From a union infer an Array and return the last element + */ +type InferLastLink = UnionToArray< + | InferSchemaLinksConfig[keyof InferSchemaLinksConfig< + ServiceName, + DmlEntity + >] +> extends [...any, infer V] + ? V + : never + +type InferLastPrimaryKey = InferLastLink< + ServiceName, + DmlEntity +> extends { + primaryKey: infer PrimaryKey +} + ? PrimaryKey + : string + +type InferLastLinkable = InferLastLink< + ServiceName, + DmlEntity +> extends { + linkable: infer Linkable +} + ? Linkable + : string + type InferPrimaryKeyNameOrNever< Schema extends DMLSchema, Key extends keyof Schema > = Schema[Key] extends PrimaryKeyModifier ? Key : never -type InferSchemaLinksConfig = T extends DmlEntity +type InferSchemaLinksConfig< + ServiceName extends string, + T +> = T extends DmlEntity ? { [K in keyof Schema as Schema[K] extends PrimaryKeyModifier ? InferPrimaryKeyNameOrNever : never]: { + serviceName: ServiceName + field: T extends DmlEntity + ? Uncapitalize> + : string linkable: InferLinkableKeyName primaryKey: K } @@ -84,7 +157,10 @@ type InferSchemaLinksConfig = T extends DmlEntity : {} /** - * From an array of DmlEntity, returns a formatted object with the linkable keys + * From an array of DmlEntity, returns a formatted links object. + * the toJSON of each object representation will return the last linkable definition + * as the default. To specify a specific linkable, you can chain until the desired linkable property. + * * * @example: * @@ -103,6 +179,8 @@ type InferSchemaLinksConfig = T extends DmlEntity * // { * // user: { * // id: { + * // serviceName: 'userService', + * // field: 'user', * // linkable: 'user_id', * // primaryKey: 'id' * // }, @@ -110,6 +188,8 @@ type InferSchemaLinksConfig = T extends DmlEntity * // }, * // car: { * // number_plate: { + * // serviceName: 'userService', + * // field: 'car', * // linkable: 'car_number_plate', * // primaryKey: 'number_plate' * // }, @@ -118,14 +198,24 @@ type InferSchemaLinksConfig = T extends DmlEntity * // } * */ -export type InfersLinksConfig[]> = - UnionToIntersection<{ - [K in keyof T as T[K] extends DmlEntity - ? Uncapitalize> - : never]: InferSchemaLinksConfig & { - toJSON: () => { - linkable: string - primaryKey: string - } +export type InfersLinksConfig< + ServiceName extends string, + T extends DmlEntity[] +> = UnionToIntersection<{ + [K in keyof T as T[K] extends DmlEntity + ? Uncapitalize> + : never]: InferSchemaLinksConfig & { + toJSON: () => { + serviceName: ServiceName + field: T[K] extends DmlEntity + ? Uncapitalize> + : string + linkable: InferLastLinkable + primaryKey: InferLastPrimaryKey } - }> + } +}> + +/** + * End Links config + */ diff --git a/packages/modules/currency/src/index.ts b/packages/modules/currency/src/index.ts index b3e6fe1043..4b97ebd8f8 100644 --- a/packages/modules/currency/src/index.ts +++ b/packages/modules/currency/src/index.ts @@ -5,8 +5,7 @@ import { Module, Modules } from "@medusajs/utils" const service = CurrencyModuleService const loaders = [initialDataLoader] -export default Module({ - name: Modules.CURRENCY, +export default Module(Modules.CURRENCY, { service, loaders, })