feat: Make the Module require the service name (#7923)

* feat: Make the Module require the service name

* finalize types
This commit is contained in:
Adrien de Peretti authored and GitHub committed 2024-07-03 15:44:57 +02:00
1 parent fdee0bd55a
commit 2711012d96
5 files changed
+165 -39

No files matched your search

@@ -458,45 +458,61 @@ describe("joiner-config-builder", () => {
number_plate: model.text().primaryKey(), number_plate: model.text().primaryKey(),
}) })
const linkConfig = buildLinkConfigFromDmlObjects([user, car]) const linkConfig = buildLinkConfigFromDmlObjects("myService", [user, car])
expectTypeOf(linkConfig).toMatchTypeOf<{ expectTypeOf(linkConfig).toMatchTypeOf<{
user: { user: {
id: { id: {
serviceName: "myService"
field: "user"
linkable: "user_id" linkable: "user_id"
primaryKey: "id" primaryKey: "id"
} }
toJSON: () => { toJSON: () => {
linkable: string serviceName: "myService"
primaryKey: string field: "user"
linkable: "user_id"
primaryKey: "id"
} }
} }
car: { car: {
number_plate: { number_plate: {
serviceName: "myService"
field: "car"
linkable: "car_number_plate" linkable: "car_number_plate"
primaryKey: "number_plate" primaryKey: "number_plate"
} }
toJSON: () => { toJSON: () => {
linkable: string serviceName: "myService"
primaryKey: string field: "car"
linkable: "car_number_plate"
primaryKey: "number_plate"
} }
} }
}>() }>()
expect(linkConfig.user.id).toEqual({ expect(linkConfig.user.id).toEqual({
serviceName: "myService",
field: "user",
linkable: "user_id", linkable: "user_id",
primaryKey: "id", primaryKey: "id",
}) })
expect(linkConfig.car.number_plate).toEqual({ expect(linkConfig.car.number_plate).toEqual({
serviceName: "myService",
field: "car",
linkable: "car_number_plate", linkable: "car_number_plate",
primaryKey: "number_plate", primaryKey: "number_plate",
}) })
expect(linkConfig.car.toJSON()).toEqual({ expect(linkConfig.car.toJSON()).toEqual({
serviceName: "myService",
field: "car",
linkable: "car_number_plate", linkable: "car_number_plate",
primaryKey: "number_plate", primaryKey: "number_plate",
}) })
expect(linkConfig.user.toJSON()).toEqual({ expect(linkConfig.user.toJSON()).toEqual({
serviceName: "myService",
field: "user",
linkable: "user_id", linkable: "user_id",
primaryKey: "id", primaryKey: "id",
}) })
@@ -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 * 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. * then it will be inferred from the entity name of the alias args.
* *
* @param moduleName * @param serviceName
* @param alias * @param alias
* @param schema * @param schema
* @param models * @param models
@@ -35,7 +35,7 @@ import { InferLinkableKeys, InfersLinksConfig } from "./types/links-config"
* @param primaryKeys * @param primaryKeys
*/ */
export function defineJoinerConfig( export function defineJoinerConfig(
moduleName: string, serviceName: string,
{ {
alias, alias,
schema, schema,
@@ -108,7 +108,7 @@ export function defineJoinerConfig(
} }
if (!primaryKeys && modelDefinitions.size) { if (!primaryKeys && modelDefinitions.size) {
const linkConfig = buildLinkConfigFromDmlObjects([ const linkConfig = buildLinkConfigFromDmlObjects(serviceName, [
...modelDefinitions.values(), ...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 // 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 { return {
serviceName: moduleName, serviceName,
primaryKeys: primaryKeys ?? ["id"], primaryKeys: primaryKeys ?? ["id"],
schema, schema,
linkableKeys: linkableKeys, linkableKeys: linkableKeys,
@@ -250,23 +250,41 @@ export function buildLinkableKeysFromMikroOrmObjects(
* test: model.text(), * test: model.text(),
* }) * })
* *
* const links = buildLinkConfigFromDmlObjects('userService', [user, car])
*
* // output: * // output:
* // { * // {
* // toJSON: function () { },
* // user: { * // user: {
* // id: "user_id", * // id: {
* // serviceName: 'userService',
* // field: 'user',
* // linkable: 'user_id',
* // primaryKey: 'id'
* // },
* // toJSON() { ... }
* // }, * // },
* // car: { * // car: {
* // number_plate: "car_number_plate", * // number_plate: {
* // }, * // serviceName: 'userService',
* // field: 'car',
* // linkable: 'car_number_plate',
* // primaryKey: 'number_plate'
* // },
* // toJSON() { ... }
* // }
* // } * // }
* *
* @param serviceName
* @param models * @param models
*/ */
export function buildLinkConfigFromDmlObjects< export function buildLinkConfigFromDmlObjects<
const ServiceName extends string,
const T extends DmlEntity<any, any>[] const T extends DmlEntity<any, any>[]
>(models: T = [] as unknown as T): InfersLinksConfig<T> { >(
const linkConfig = {} as InfersLinksConfig<T> serviceName: ServiceName,
models: T = [] as unknown as T
): InfersLinksConfig<ServiceName, T> {
const linkConfig = {} as InfersLinksConfig<ServiceName, T>
for (const model of models) { for (const model of models) {
if (!DmlEntity.isDmlEntity(model)) { if (!DmlEntity.isDmlEntity(model)) {
@@ -297,12 +315,14 @@ export function buildLinkConfigFromDmlObjects<
modelLinkConfig[property] = { modelLinkConfig[property] = {
linkable: linkableKeyName, linkable: linkableKeyName,
primaryKey: property, primaryKey: property,
serviceName,
field: lowerCaseFirst(model.name),
} }
} }
} }
} }
return linkConfig as InfersLinksConfig<T> & Record<any, any> return linkConfig as InfersLinksConfig<ServiceName, T> & Record<any, any>
} }
/** /**
+10 -9
View File
@@ -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 * 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 * return a links object based on the DML objects
* @param moduleName *
* @param serviceName
* @param service * @param service
* @param loaders * @param loaders
* @constructor * @constructor
*/ */
export function Module< export function Module<
const ServiceName extends string,
const Service extends Constructor<any>, const Service extends Constructor<any>,
const ModelObjects extends DmlEntity<any, any>[] = Service extends { const ModelObjects extends DmlEntity<any, any>[] = Service extends {
$modelObjects: infer $DmlObjects $modelObjects: infer $DmlObjects
@@ -24,22 +26,21 @@ export function Module<
: [], : [],
Links = keyof ModelObjects extends never Links = keyof ModelObjects extends never
? Record<string, any> ? Record<string, any>
: InfersLinksConfig<ModelObjects> : InfersLinksConfig<ServiceName, ModelObjects>
>({ >(
name = "", serviceName: ServiceName,
service, { service, loaders }: ModuleExports<Service>
loaders, ): ModuleExports<Service> & {
}: ModuleExports<Service> & { name?: string }): ModuleExports<Service> & {
links: Links links: Links
} { } {
service.prototype.__joinerConfig ??= defineJoinerConfig(name) service.prototype.__joinerConfig ??= defineJoinerConfig(serviceName)
const dmlObjects = service[MedusaServiceModelObjectsSymbol] const dmlObjects = service[MedusaServiceModelObjectsSymbol]
return { return {
service, service,
loaders, loaders,
links: (dmlObjects?.length links: (dmlObjects?.length
? buildLinkConfigFromDmlObjects(dmlObjects) ? buildLinkConfigFromDmlObjects<ServiceName, ModelObjects>(dmlObjects)
: {}) as Links, : {}) as Links,
} }
} }
@@ -7,6 +7,10 @@ import {
import { DmlEntity } from "../../dml" import { DmlEntity } from "../../dml"
import { PrimaryKeyModifier } from "../../dml/properties/primary-key" import { PrimaryKeyModifier } from "../../dml/properties/primary-key"
/**
* Utils
*/
type FlattenUnion<T> = T extends { [K in keyof T]: infer U } type FlattenUnion<T> = T extends { [K in keyof T]: infer U }
? { [K in keyof T]: U } ? { [K in keyof T]: U }
: never : never
@@ -17,6 +21,30 @@ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
? I ? I
: never : never
type UnionToOvlds<U> = UnionToIntersection<
U extends any ? (f: U) => void : never
>
type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true
type UnionToArray<T, A extends unknown[] = []> = IsUnion<T> extends true
? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
: [T, ...A]
type Reverse<T extends unknown[], R extends unknown[] = []> = ReturnType<
T extends [infer F, ...infer L] ? () => Reverse<L, [F, ...R]> : () => R
>
/**
* End of utils
*/
/**
* Linkable keys
*/
type InferLinkableKeyName< type InferLinkableKeyName<
Key, Key,
Property, Property,
@@ -67,16 +95,61 @@ type AggregateSchemasLinkableKeys<T extends DmlEntity<any, any>[]> = {
export type InferLinkableKeys<T extends DmlEntity<any, any>[]> = export type InferLinkableKeys<T extends DmlEntity<any, any>[]> =
UnionToIntersection<FlattenUnion<AggregateSchemasLinkableKeys<T>>[0]> UnionToIntersection<FlattenUnion<AggregateSchemasLinkableKeys<T>>[0]>
/**
* End Linkable keys
*/
/**
* Links config
*/
/**
* From a union infer an Array and return the last element
*/
type InferLastLink<ServiceName extends string, DmlEntity> = UnionToArray<
| InferSchemaLinksConfig<ServiceName, DmlEntity>[keyof InferSchemaLinksConfig<
ServiceName,
DmlEntity
>]
> extends [...any, infer V]
? V
: never
type InferLastPrimaryKey<ServiceName extends string, DmlEntity> = InferLastLink<
ServiceName,
DmlEntity
> extends {
primaryKey: infer PrimaryKey
}
? PrimaryKey
: string
type InferLastLinkable<ServiceName extends string, DmlEntity> = InferLastLink<
ServiceName,
DmlEntity
> extends {
linkable: infer Linkable
}
? Linkable
: string
type InferPrimaryKeyNameOrNever< type InferPrimaryKeyNameOrNever<
Schema extends DMLSchema, Schema extends DMLSchema,
Key extends keyof Schema Key extends keyof Schema
> = Schema[Key] extends PrimaryKeyModifier<any, any> ? Key : never > = Schema[Key] extends PrimaryKeyModifier<any, any> ? Key : never
type InferSchemaLinksConfig<T> = T extends DmlEntity<infer Schema, infer Config> type InferSchemaLinksConfig<
ServiceName extends string,
T
> = T extends DmlEntity<infer Schema, infer Config>
? { ? {
[K in keyof Schema as Schema[K] extends PrimaryKeyModifier<any, any> [K in keyof Schema as Schema[K] extends PrimaryKeyModifier<any, any>
? InferPrimaryKeyNameOrNever<Schema, K> ? InferPrimaryKeyNameOrNever<Schema, K>
: never]: { : never]: {
serviceName: ServiceName
field: T extends DmlEntity<any, infer Config>
? Uncapitalize<InferDmlEntityNameFromConfig<Config>>
: string
linkable: InferLinkableKeyName<K, Schema[K], Config> linkable: InferLinkableKeyName<K, Schema[K], Config>
primaryKey: K primaryKey: K
} }
@@ -84,7 +157,10 @@ type InferSchemaLinksConfig<T> = T extends DmlEntity<infer Schema, infer Config>
: {} : {}
/** /**
* 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: * @example:
* *
@@ -103,6 +179,8 @@ type InferSchemaLinksConfig<T> = T extends DmlEntity<infer Schema, infer Config>
* // { * // {
* // user: { * // user: {
* // id: { * // id: {
* // serviceName: 'userService',
* // field: 'user',
* // linkable: 'user_id', * // linkable: 'user_id',
* // primaryKey: 'id' * // primaryKey: 'id'
* // }, * // },
@@ -110,6 +188,8 @@ type InferSchemaLinksConfig<T> = T extends DmlEntity<infer Schema, infer Config>
* // }, * // },
* // car: { * // car: {
* // number_plate: { * // number_plate: {
* // serviceName: 'userService',
* // field: 'car',
* // linkable: 'car_number_plate', * // linkable: 'car_number_plate',
* // primaryKey: 'number_plate' * // primaryKey: 'number_plate'
* // }, * // },
@@ -118,14 +198,24 @@ type InferSchemaLinksConfig<T> = T extends DmlEntity<infer Schema, infer Config>
* // } * // }
* *
*/ */
export type InfersLinksConfig<T extends DmlEntity<any, any>[]> = export type InfersLinksConfig<
UnionToIntersection<{ ServiceName extends string,
[K in keyof T as T[K] extends DmlEntity<any, infer Config> T extends DmlEntity<any, any>[]
? Uncapitalize<InferDmlEntityNameFromConfig<Config>> > = UnionToIntersection<{
: never]: InferSchemaLinksConfig<T[K]> & { [K in keyof T as T[K] extends DmlEntity<any, infer Config>
toJSON: () => { ? Uncapitalize<InferDmlEntityNameFromConfig<Config>>
linkable: string : never]: InferSchemaLinksConfig<ServiceName, T[K]> & {
primaryKey: string toJSON: () => {
} serviceName: ServiceName
field: T[K] extends DmlEntity<any, infer Config>
? Uncapitalize<InferDmlEntityNameFromConfig<Config>>
: string
linkable: InferLastLinkable<ServiceName, T[K]>
primaryKey: InferLastPrimaryKey<ServiceName, T[K]>
} }
}> }
}>
/**
* End Links config
*/
+1 -2
View File
@@ -5,8 +5,7 @@ import { Module, Modules } from "@medusajs/utils"
const service = CurrencyModuleService const service = CurrencyModuleService
const loaders = [initialDataLoader] const loaders = [initialDataLoader]
export default Module({ export default Module(Modules.CURRENCY, {
name: Modules.CURRENCY,
service, service,
loaders, loaders,
}) })