|
| 1 | +import { ConditionalKeys, IsAny, LiteralUnion, ValueOf } from 'type-fest'; |
| 2 | +import { DBName, ResourceShape } from '~/common'; |
| 3 | +import { ResourceDBMap, ResourceMap } from './map'; |
| 4 | + |
| 5 | +export type AllResourceNames = keyof ResourceMap; |
| 6 | +export type AllResourceDBNames = DBName<ValueOf<ResourceDBMap>>; |
| 7 | +export type ResourceNameLike = LiteralUnion<AllResourceNames, string>; |
| 8 | + |
| 9 | +//region ResourceName |
| 10 | +/** |
| 11 | + * Given some sort of identifier for a resource type, |
| 12 | + * return our resource name if known, otherwise never. |
| 13 | + * |
| 14 | + * @example User |
| 15 | + * "User" -> "User" |
| 16 | + * "default::User" -> "User" |
| 17 | + * typeof User -> "User" |
| 18 | + * User -> "User" |
| 19 | + * |
| 20 | + * @example Including subclasses |
| 21 | + * |
| 22 | + * ResourceName<"Engagement", true> |
| 23 | + * -> "Engagement" | "LanguageEngagement" | "InternshipEngagement" |
| 24 | + * |
| 25 | + * @example Edge Cases |
| 26 | + * any -> string |
| 27 | + * ResourceShape<any> -> string |
| 28 | + * "foo" -> never |
| 29 | + * Foo -> never |
| 30 | + */ |
| 31 | +export type ResourceName< |
| 32 | + T, |
| 33 | + IncludeSubclasses extends boolean = false, |
| 34 | +> = IsAny<T> extends true |
| 35 | + ? string // short-circuit and prevent many seemly random circular definitions |
| 36 | + : T extends AllResourceDBNames |
| 37 | + ? ResourceNameFromStatic< |
| 38 | + ResourceMap[ResourceNameFromDBName<T>], |
| 39 | + IncludeSubclasses |
| 40 | + > |
| 41 | + : T extends AllResourceNames |
| 42 | + ? ResourceNameFromStatic<ResourceMap[T], IncludeSubclasses> |
| 43 | + : T extends ResourceShape<any> |
| 44 | + ? ResourceNameFromStatic<T, IncludeSubclasses> |
| 45 | + : ResourceNameFromInstance<T> extends string |
| 46 | + ? ResourceNameFromInstance<T, IncludeSubclasses> & string |
| 47 | + : never; |
| 48 | + |
| 49 | +type ResourceNameFromInstance< |
| 50 | + TResource, |
| 51 | + IncludeSubclasses extends boolean = false, |
| 52 | +> = { |
| 53 | + [Name in keyof ResourceMap]: ResourceMap[Name]['prototype'] extends TResource // Only self or subclasses |
| 54 | + ? IncludeSubclasses extends true |
| 55 | + ? Name |
| 56 | + : TResource extends ResourceMap[Name]['prototype'] // Exclude subclasses |
| 57 | + ? Name |
| 58 | + : never |
| 59 | + : never; |
| 60 | +}[keyof ResourceMap]; |
| 61 | + |
| 62 | +type ResourceNameFromStatic< |
| 63 | + TResourceStatic extends ResourceShape<any>, |
| 64 | + IncludeSubclasses extends boolean = false, |
| 65 | +> = ResourceShape<any> extends TResourceStatic |
| 66 | + ? string // short-circuit non-specific types |
| 67 | + : { |
| 68 | + [Name in keyof ResourceMap]: ResourceMap[Name] extends TResourceStatic // Only self or subclasses |
| 69 | + ? IncludeSubclasses extends true |
| 70 | + ? Name |
| 71 | + : TResourceStatic extends ResourceMap[Name] // Exclude subclasses |
| 72 | + ? Name |
| 73 | + : never |
| 74 | + : never; |
| 75 | + }[keyof ResourceMap]; |
| 76 | + |
| 77 | +type ResourceNameFromDBName<Name extends AllResourceDBNames> = |
| 78 | + // eslint-disable-next-line @typescript-eslint/naming-convention |
| 79 | + ConditionalKeys<ResourceDBMap, { __element__: { __name__: Name } }>; |
| 80 | +//endregion |
| 81 | + |
| 82 | +export type ResourceStaticFromName<Name> = string extends Name |
| 83 | + ? ResourceShape<any> |
| 84 | + : Name extends keyof ResourceMap |
| 85 | + ? ValueOf<Pick<ResourceMap, Name>> |
| 86 | + : never; |
0 commit comments