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
2024-07-03 15:44:57 +02:00
committed by GitHub
parent fdee0bd55a
commit 2711012d96
5 changed files with 165 additions and 39 deletions

View File

@@ -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",
})

View File

@@ -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<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) {
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<T> & Record<any, any>
return linkConfig as InfersLinksConfig<ServiceName, T> & Record<any, any>
}
/**

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
* 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<any>,
const ModelObjects extends DmlEntity<any, any>[] = Service extends {
$modelObjects: infer $DmlObjects
@@ -24,22 +26,21 @@ export function Module<
: [],
Links = keyof ModelObjects extends never
? Record<string, any>
: InfersLinksConfig<ModelObjects>
>({
name = "",
service,
loaders,
}: ModuleExports<Service> & { name?: string }): ModuleExports<Service> & {
: InfersLinksConfig<ServiceName, ModelObjects>
>(
serviceName: ServiceName,
{ service, loaders }: ModuleExports<Service>
): ModuleExports<Service> & {
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<ServiceName, ModelObjects>(dmlObjects)
: {}) as Links,
}
}

View File

@@ -7,6 +7,10 @@ import {
import { DmlEntity } from "../../dml"
import { PrimaryKeyModifier } from "../../dml/properties/primary-key"
/**
* Utils
*/
type FlattenUnion<T> = T extends { [K in keyof T]: infer U }
? { [K in keyof T]: U }
: never
@@ -17,6 +21,30 @@ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
? I
: 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<
Key,
Property,
@@ -67,16 +95,61 @@ type AggregateSchemasLinkableKeys<T extends DmlEntity<any, any>[]> = {
export type InferLinkableKeys<T extends DmlEntity<any, any>[]> =
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<
Schema extends DMLSchema,
Key extends keyof Schema
> = 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>
? InferPrimaryKeyNameOrNever<Schema, K>
: never]: {
serviceName: ServiceName
field: T extends DmlEntity<any, infer Config>
? Uncapitalize<InferDmlEntityNameFromConfig<Config>>
: string
linkable: InferLinkableKeyName<K, Schema[K], Config>
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:
*
@@ -103,6 +179,8 @@ type InferSchemaLinksConfig<T> = T extends DmlEntity<infer Schema, infer Config>
* // {
* // user: {
* // id: {
* // serviceName: 'userService',
* // field: 'user',
* // linkable: 'user_id',
* // primaryKey: 'id'
* // },
@@ -110,6 +188,8 @@ type InferSchemaLinksConfig<T> = T extends DmlEntity<infer Schema, infer Config>
* // },
* // car: {
* // number_plate: {
* // serviceName: 'userService',
* // field: 'car',
* // linkable: 'car_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>[]> =
UnionToIntersection<{
[K in keyof T as T[K] extends DmlEntity<any, infer Config>
? Uncapitalize<InferDmlEntityNameFromConfig<Config>>
: never]: InferSchemaLinksConfig<T[K]> & {
toJSON: () => {
linkable: string
primaryKey: string
}
export type InfersLinksConfig<
ServiceName extends string,
T extends DmlEntity<any, any>[]
> = UnionToIntersection<{
[K in keyof T as T[K] extends DmlEntity<any, infer Config>
? Uncapitalize<InferDmlEntityNameFromConfig<Config>>
: never]: InferSchemaLinksConfig<ServiceName, T[K]> & {
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
*/