Skip to content

Commit dd3ebbf

Browse files
committed
Rework ResourceNames
1 parent eb1898b commit dd3ebbf

File tree

4 files changed

+103
-34
lines changed

4 files changed

+103
-34
lines changed

src/common/resource.dto.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { LazyGetter as Once } from 'lazy-get-decorator';
44
import { DateTime } from 'luxon';
55
import { keys as keysOf } from 'ts-transformer-keys';
66
import { inspect } from 'util';
7-
import type { ResourceDBMap, ResourceMap } from '~/core';
7+
import type { ResourceDBMap, ResourceName } from '~/core';
88
import { $, e } from '~/core/edgedb/reexports';
99
import { ScopedRole } from '../components/authorization';
1010
import { CalculatedSymbol } from './calculated.decorator';
@@ -284,23 +284,11 @@ export const isResourceClass = <T>(
284284
): cls is ResourceShape<T> =>
285285
'Props' in cls && Array.isArray(cls.Props) && cls.Props.length > 0;
286286

287-
export type ResourceName<TResourceStatic extends ResourceShape<any>> =
288-
ResourceShape<any> extends TResourceStatic
289-
? string // short-circuit non-specific types
290-
: {
291-
[Name in keyof ResourceMap]: ResourceMap[Name] extends TResourceStatic // Only self or subclasses
292-
? TResourceStatic extends ResourceMap[Name] // Exclude subclasses
293-
? Name
294-
: never
295-
: never;
296-
}[keyof ResourceMap] &
297-
string;
298-
299287
export type DBType<TResourceStatic extends ResourceShape<any>> =
300288
ResourceShape<any> extends TResourceStatic
301289
? typeof e.Resource // short-circuit non-specific types
302-
: ResourceName<TResourceStatic> extends keyof ResourceDBMap
303-
? ResourceDBMap[ResourceName<TResourceStatic>] extends infer T extends $.$expr_PathNode
290+
: ResourceName<TResourceStatic> extends `${infer Name extends keyof ResourceDBMap}`
291+
? ResourceDBMap[Name] extends infer T extends $.$expr_PathNode
304292
? T
305293
: never
306294
: never;

src/core/resources/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export * from './resource-resolver.service';
22
export { LoaderFactory } from './loader.registry';
33
export * from './resource.loader';
44
export * from './resources.host';
5+
export * from './resource-name.types';
56
export * from './resource.decorator';
67
export * from './map';
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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;

src/core/resources/resources.host.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,30 @@ import { GraphQLSchemaHost } from '@nestjs/graphql';
33
import { CachedByArg, mapKeys } from '@seedcompany/common';
44
import { isObjectType } from 'graphql';
55
import { mapValues } from 'lodash';
6-
import { ConditionalKeys, LiteralUnion, ValueOf } from 'type-fest';
6+
import { ValueOf } from 'type-fest';
77
import {
8-
DBName,
98
EnhancedResource,
109
InvalidIdForTypeException,
1110
ResourceShape,
1211
ServerException,
1312
} from '~/common';
14-
import { ResourceDBMap, ResourceMap } from './map';
13+
import { ResourceMap } from './map';
1514
import { __privateDontUseThis } from './resource-map-holder';
15+
import {
16+
AllResourceDBNames,
17+
ResourceName,
18+
ResourceNameLike,
19+
ResourceStaticFromName,
20+
} from './resource-name.types';
1621

1722
export type EnhancedResourceMap = {
1823
[K in keyof ResourceMap]: EnhancedResource<ResourceMap[K]>;
1924
};
2025

21-
type LooseResourceName = LiteralUnion<keyof ResourceMap, string>;
22-
2326
export type ResourceLike =
2427
| ResourceShape<any>
2528
| EnhancedResource<any>
26-
| LooseResourceName;
27-
28-
type ResourceNameFromDBName<K extends DBName<ValueOf<ResourceDBMap>>> =
29-
// eslint-disable-next-line @typescript-eslint/naming-convention
30-
ConditionalKeys<ResourceDBMap, { __element__: { __name__: K } }>;
29+
| ResourceNameLike;
3130

3231
@Injectable()
3332
export class ResourcesHost {
@@ -66,18 +65,13 @@ export class ResourcesHost {
6665
return resource;
6766
}
6867

69-
getByDynamicName(name: LooseResourceName): EnhancedResource<any> {
68+
getByDynamicName(name: ResourceNameLike): EnhancedResource<any> {
7069
return this.getByName(name as any);
7170
}
7271

73-
getByEdgeDB<K extends keyof ResourceMap>(
74-
name: K,
75-
): EnhancedResource<ValueOf<Pick<ResourceMap, K>>>;
76-
getByEdgeDB<K extends DBName<ValueOf<ResourceDBMap>>>(
77-
name: K,
78-
): EnhancedResource<ValueOf<Pick<ResourceMap, ResourceNameFromDBName<K>>>>;
79-
getByEdgeDB(name: string): EnhancedResource<any>;
80-
getByEdgeDB(name: string) {
72+
getByEdgeDB<Name extends ResourceNameLike | AllResourceDBNames>(
73+
name: Name,
74+
): EnhancedResource<ResourceStaticFromName<ResourceName<Name>>> {
8175
const fqnMap = this.edgeDBFQNMap();
8276
const resByFQN = fqnMap.get(
8377
name.includes('::') ? name : `default::${name}`,

0 commit comments

Comments
 (0)