Skip to content

Commit

Permalink
feat: Autocomplete all include parameters and make lookup generic
Browse files Browse the repository at this point in the history
Replace all overloads of the `lookup` method with one generic interface.
Now that we have `EntityTypeMap` and `EntityIncludeMap` this approach no
longer causes "excessively deep type" errors.

The trick to make this work is to pass the generic parameter to the map
before accessing its properties, since accesssed properties of the map
can not be generic on their own.

Lots of imports and an unused helper type can now be dropped as well.
  • Loading branch information
kellnerd committed Mar 27, 2024
1 parent 87fb6f3 commit 5fb049a
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 123 deletions.
41 changes: 25 additions & 16 deletions api_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,29 @@ export type IsoDate =
*/
export type Locale = string;

/** Maps entity type names to their type definitions. */
export type EntityTypeMap = {
area: Area;
artist: Artist;
collection: Collection;
event: Event;
genre: Genre;
instrument: Instrument;
label: Label;
place: Place;
recording: Recording;
release: Release;
"release-group": ReleaseGroup;
series: Series;
work: Work;
url: Url;
/**
* Maps entity type names to their type definitions.
*
* Accepts any include parameters, but only passes the supported
* include parameters to each entity type.
*/
export type EntityTypeMap<Include extends AnyInclude> = {
area: Area<Include extends AreaInclude ? Include : never>;
artist: Artist<Include extends ArtistInclude ? Include : never>;
collection: Collection<Include extends CollectionInclude ? Include : never>;
event: Event<Include extends EventInclude ? Include : never>;
genre: Genre<Include extends GenreInclude ? Include : never>;
instrument: Instrument<Include extends InstrumentInclude ? Include : never>;
label: Label<Include extends LabelInclude ? Include : never>;
place: Place<Include extends PlaceInclude ? Include : never>;
recording: Recording<Include extends RecordingInclude ? Include : never>;
release: Release<Include extends ReleaseInclude ? Include : never>;
"release-group": ReleaseGroup<
Include extends ReleaseGroupInclude ? Include : never
>;
series: Series<Include extends SeriesInclude ? Include : never>;
work: Work<Include extends WorkInclude ? Include : never>;
url: Url<Include extends UrlInclude ? Include : never>;
};

/** Maps entity type names to their possible include parameter value types. */
Expand Down Expand Up @@ -750,3 +757,5 @@ export type Url<Include extends UrlInclude = never> = WithIncludes<

/** All possible include parameter values for all entity types. */
export type AnyInclude = EntityIncludeMap[keyof EntityIncludeMap];

export type AnyEntity<Include extends AnyInclude = AnyInclude> = EntityWithMbid;
108 changes: 6 additions & 102 deletions client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,14 @@

import { delay } from "@std/async/delay";
import type {
AnyInclude,
Area,
AreaInclude,
Artist,
ArtistInclude,
Collection,
CollectionWithContents,
EntityWithMbid,
Event,
EventInclude,
Genre,
Instrument,
InstrumentInclude,
Label,
LabelInclude,
EntityIncludeMap,
EntityTypeMap,
MBID,
Place,
PlaceInclude,
Recording,
RecordingInclude,
Release,
ReleaseGroup,
ReleaseGroupInclude,
ReleaseInclude,
Series,
SeriesInclude,
Url,
UrlInclude,
Work,
WorkInclude,
} from "./api_types.ts";
import { ApiError, isError } from "./error.ts";
import type { CollectableEntityType, EntityType } from "./data/entity.ts";
import { assertMbid, entityPlural } from "./utils/entity.ts";
import type { UnionTypeOrNever } from "./utils/type_utils.ts";

/** MusicBrainz API client configuration options. */
export interface ClientOptions {
Expand Down Expand Up @@ -101,80 +74,11 @@ export class MusicBrainzClient {
}

/** Performs a lookup request for the given entity. */
lookup<Include extends ReleaseInclude = never>(
entityType: "release",
lookup<Type extends EntityType, Include extends EntityIncludeMap[Type]>(
entityType: Type,
mbid: MBID,
inc?: Include[],
): Promise<Release<Include>>;
lookup<Include extends AreaInclude = never>(
entityType: "area",
mbid: MBID,
inc?: Include[],
): Promise<Area<Include>>;
lookup<Include extends ArtistInclude = never>(
entityType: "artist",
mbid: MBID,
inc?: Include[],
): Promise<Artist<Include>>;
lookup(
entityType: "collection",
mbid: MBID,
): Promise<Collection>;
lookup<Include extends EventInclude = never>(
entityType: "event",
mbid: MBID,
inc?: Include[],
): Promise<Event<Include>>;
lookup(
entityType: "genre",
mbid: MBID,
): Promise<Genre>;
lookup<Include extends InstrumentInclude = never>(
entityType: "instrument",
mbid: MBID,
inc?: Include[],
): Promise<Instrument<Include>>;
lookup<Include extends LabelInclude = never>(
entityType: "label",
mbid: MBID,
inc?: Include[],
): Promise<Label<Include>>;
lookup<Include extends PlaceInclude = never>(
entityType: "place",
mbid: MBID,
inc?: Include[],
): Promise<Place<Include>>;
lookup<Include extends RecordingInclude = never>(
entityType: "recording",
mbid: MBID,
inc?: Include[],
): Promise<Recording<Include>>;
lookup<Include extends ReleaseGroupInclude = never>(
entityType: "release-group",
mbid: MBID,
inc?: Include[],
): Promise<ReleaseGroup<Include>>;
lookup<Include extends SeriesInclude = never>(
entityType: "series",
mbid: MBID,
inc?: Include[],
): Promise<Series<Include>>;
lookup<Include extends UrlInclude = never>(
entityType: "url",
mbid: MBID,
inc?: Include[],
): Promise<Url<Include>>;
lookup<Include extends WorkInclude = never>(
entityType: "work",
mbid: MBID,
inc?: Include[],
): Promise<Work<Include>>;
lookup<T extends EntityType>(
entityType: UnionTypeOrNever<T>, // only accepts an undetermined type
mbid: MBID,
inc?: AnyInclude[],
): Promise<EntityWithMbid>;
lookup(entityType: EntityType, mbid: MBID, inc: string[] = []) {
inc: Include[] = [],
): Promise<EntityTypeMap<Include>[Type]> {
assertMbid(mbid);
return this.get([entityType, mbid].join("/"), { inc: inc.join("+") });
}
Expand Down
2 changes: 2 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export type {
ArtistInclude,
Collection,
CollectionWithContents,
EntityIncludeMap,
EntityTypeMap,
EntityWithMbid,
Event,
EventInclude,
Expand Down
5 changes: 0 additions & 5 deletions utils/type_utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
/** Transforms a string type from kebab case to snake case. */
export type SnakeCase<KebabCase extends string> = KebabCase extends
`${infer A}-${infer B}` ? `${A}_${SnakeCase<B>}` : KebabCase;

/** Restricts the given type to be a union type. */
export type UnionTypeOrNever<T, U extends T = T> = T extends unknown
? [U] extends [T] ? never : T
: never;

0 comments on commit 5fb049a

Please sign in to comment.