-
Notifications
You must be signed in to change notification settings - Fork 1
/
api_includes.ts
151 lines (138 loc) · 5.75 KB
/
api_includes.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import type { RelatableEntityType } from "./data/entity.ts";
/** Include parameters can be specified to request more information from the API. */
export type IncludeParameter = string;
/**
* Helper type to mark a property of an entity as optional sub-query.
* The additional {@linkcode Data} from a sub-query is only present in API
* responses if the {@linkcode RequiredInclude} parameter is specified.
*
* Types which are using this helper have to be unwrapped again before usage,
* {@linkcode WithIncludes} or {@linkcode Unwrap} will do this.
*/
export type $SubQuery<Data, RequiredInclude extends IncludeParameter> = {
readonly __inc__: RequiredInclude;
readonly __data__: Data;
};
/**
* {@linkcode Data} with additional information for the given include parameters.
*
* Recursively unwraps the data and removes unavailable properties.
*/
export type WithIncludes<
Data,
Include extends IncludeParameter,
> = Data extends object ? Pick<
UnwrapProperties<Data, Include>,
AvailableKeys<Data, Include>
>
// Leave primitive values alone, there is nothing to unwrap.
: Data;
/** Recursively unwraps the data and removes all sub-query properties. */
export type Unwrap<Data> = WithIncludes<Data, never>;
/**
* Keys of all properties which are included in {@linkcode Entity} for the given
* {@linkcode Include} parameters.
*/
export type AvailableKeys<
Entity extends object,
Include extends IncludeParameter,
> = Exclude<
{
[Key in keyof Entity]:
// Check if the value is a sub-query and infer its include type.
// Exclude `undefined` from value to also detect optional sub-queries.
Exclude<Entity[Key], undefined> extends
$SubQuery<infer _Data, infer RequiredInclude>
// Return key if the required include parameter is specified.
? RequiredInclude extends Include ? Key : never
// Always return the key of regular properties.
: Key;
}[keyof Entity],
// TS allows optional properties to be `undefined` by default, exclude it.
undefined
>;
/**
* Recursively unwraps the data of all {@linkcode $SubQuery} properties for which
* the required {@linkcode Include} parameters have been specified and removes
* all sub-queries (by replacing them with `never`) for which this is not the case.
*
* - Pass `never` as {@linkcode Include} to omit all sub-queries.
* - Pass {@linkcode IncludeParameter} to include all sub-queries.
*/
export type UnwrapProperties<
Entity extends object,
Include extends IncludeParameter,
> = {
// Process all properties (preserves optionality), keys still have to be filtered later.
[Key in keyof Entity]:
// Check if the value is a sub-query and infer its data and include type.
Exclude<Entity[Key], undefined> extends
$SubQuery<infer Data, infer RequiredInclude>
// Unwrap the sub-query if the required include parameter is specified.
? RequiredInclude extends Include ? UnwrapData<Data, Include> : never
// Always unwrap regular properties to find nested sub-queries.
: UnwrapData<Entity[Key], Include>;
};
/** Applies the sub-query data unwrapping for all objects from the given data. */
type UnwrapData<Data, Include extends IncludeParameter> =
// Skip empty arrays, they cause trouble when inferring their item type.
Data extends never[] ? []
// Each item of a data array has to be unwrapped individually (except primitives).
: Data extends Array<infer Item>
// Turn off distributivity to leave primitive union types alone.
? [Item] extends [string | number | undefined] ? Item[]
: WithIncludes<Item, Include>[]
: WithIncludes<Data, Include>;
/**
* All possible include parameter values for the given {@linkcode Entity}.
*
* Recursively collects {@linkcode IncludeParameter} values which affect the
* presence of sub-query properties from the given entity type and its children.
*/
export type CollectIncludes<Entity extends object> = Exclude<
{
[Key in keyof Entity]:
// Check if the value is a sub-query and infer its data and include type.
Exclude<Entity[Key], undefined> extends
$SubQuery<infer Data, infer RequiredInclude>
// Return the includes of the sub-query and collect those from its data.
? (RequiredInclude | CollectSubQueryIncludes<Data>)
// Collect includes from all regular child properties.
: CollectSubQueryIncludes<Entity[Key]>;
}[StringKeyOf<Entity>], // Lookup the collected include value of each property.
undefined // Optional entity properties will add `undefined` to the type.
>;
type StringKeyOf<T> = keyof T extends string ? keyof T : never;
/** Collects sub-query include parameters from all objects of the given data. */
type CollectSubQueryIncludes<Data> =
// Collect includes of the items of a data array instead of the array itself.
Data extends Array<infer Item extends object> ? CollectIncludes<Item>
: Data extends object ? CollectIncludes<Data>
// Leave scalar values alone, there is nothing to collect.
: never;
/** Derives the possible relationship target entity types from the given include parameters. */
export type PossibleRelTargetType<Include extends IncludeParameter> =
Include extends `${infer Type extends RelatableEntityType}-rels` ? Type
: never;
/** Miscellaneous includes which can be used for (almost) all entity types. */
const miscIncludes = [
"aliases",
"annotation",
"tags",
"genres",
"ratings",
// Tags/genres/ratings submitted by the specified user
"user-tags",
"user-genres",
"user-ratings",
] as const;
/** How much of the data about the linked entities should be included. */
const subQueryIncludes = [
// Disc IDs for all media
"discids",
// Media for all releases (number of tracks and format)
"media",
// ISRCs for all recordings
"isrcs",
"artist-credits",
] as const;