Skip to content

Commit

Permalink
explain some type wizardry
Browse files Browse the repository at this point in the history
Signed-off-by: eternal-flame-AD <[email protected]>
  • Loading branch information
eternal-flame-AD committed Sep 3, 2024
1 parent b1def1e commit 45916a8
Showing 1 changed file with 20 additions and 8 deletions.
28 changes: 20 additions & 8 deletions js/igv.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ type AnyCase<T extends string> = Uppercase<T> | Lowercase<T>;
// any action that can be deferred as a promise or a thenable
type Deferrable<T> = T | Promise<T> | Thenable<T, any> | (() => T | Promise<T> | Thenable<T, any>);

// a custom future implementation supported by igv.js
export interface Thenable<T, E> {
then(resolve: (value: T) => void, reject: (error: E) => void): void;
}

// simply prevents the type from being inferred
declare class Opaque<N extends string> {
private readonly __opaque_brand: N;
}
Expand All @@ -18,6 +20,7 @@ export type TrackType =
/* undocumented options */
"snp" | "eqtl";

// converts a track type into the options needed to create it
export type TrackLoad<T extends TrackType> =
Tracks.TrackCommonOptions &
(T extends "annotation" ? Tracks.AnnotationTrackOptions & TypeFormatPair<'annotation'> :
Expand All @@ -40,23 +43,27 @@ export type TrackLoad<T extends TrackType> =

export type TypeFormatPair<T extends TrackType> = {
type?: T;
// if we know the format, type is not required
format: TrackFormatOf<T>;
url?: string;
} | {
type?: T;
format?: TrackFormatOf<T>;
// if we can infer the URL, we do not need type or format
url: TrackFormatOf<T> extends string ? Deferrable<URLInference.URLWithExtension<TrackFormatOf<T>>> : never;
} | ({
type?: T;
// an explicit type and manual reader is allowed, however format and url will not make sense in this context
type: T;
format?: never;
url?: never;
} & (CustomReaderOf<T> | ManualFeatureOf<T>)) | {
type: T;
format?: TrackFormatOf<T>;
// if there is only one possible format, we permit it to be omitted
url: TrackFormatOf<T> extends string ? (string extends TrackFormatOf<T> ? never : string) : never;
// if there is only one possible format, we permit format to be omitted
url: TrackFormatOf<T> extends string ? (string extends TrackFormatOf<T> ? never : Deferrable<URLInference.URLWithExtension<TrackFormatOf<T>>>) : never;
};

// converts a track type into the file formats that it can accept
export type TrackFormatOf<T extends TrackType> =
T extends "annotation" ? Tracks.AnnotationFormat :
T extends "wig" ? Tracks.WigFormat :
Expand All @@ -75,6 +82,7 @@ export type TrackFormatOf<T extends TrackType> =
T extends "eqtl" ? Tracks.EQTLFormat :
never;

// converts a track type into the manual features that it can accept
export type ManualFeatureOf<T extends TrackType> =
T extends "annotation" ? {
features: Record<string, any>[];
Expand All @@ -83,15 +91,16 @@ export type ManualFeatureOf<T extends TrackType> =
features: Tracks.WigTrackManualFeatures;
} :
T extends "seg" ? {
features: Array<{
features: {
chr: string;
start: number;
end: number;
value: number;
sample: string;
}>;
}[];
} : never;

// some tracks can support a custom reader that polls data based on options
export type CustomReaderOf<T extends TrackType> =
T extends "annotation" ? { reader: Tracks.AnnotationCustomReader } |
{
Expand All @@ -113,6 +122,7 @@ export type CustomReaderOf<T extends TrackType> =
} :
never;

// converts a track type into the actual track class
export type TrackOf<T extends TrackType> =
T extends "annotation" ? Tracks.Track :
T extends "wig" ? Tracks.Track :
Expand Down Expand Up @@ -151,10 +161,12 @@ export namespace URLInference {

type AuxExtensions = ".gz" | ".tab" | ".txt" | ".bgz";

// infer the extension of a URL, with auxillary extensions stripped
export type InferExtension<F extends string> = Lowercase<F> extends `${infer B}${AuxExtensions}` ? StripLast<B, '.'> : StripLast<Lowercase<F>, '.'>

type Query = `?${string}`;

// matches a URL with an extension
export type URLWithExtension<E extends string> =
`${string}.${E}${AuxExtensions | ''}${Query | ''}`;
}
Expand Down Expand Up @@ -370,7 +382,7 @@ export namespace Tracks {
*
* @type {object}
*/
guidelines?: Array<{ color: string, y: number, dotted: boolean }>;
guidelines?: { color: string, y: number, dotted: boolean }[];
/**
* Type of graph, either "bar" or "points"
*
Expand Down Expand Up @@ -634,8 +646,6 @@ export namespace Tracks {
}

type Nucleotide = 'A' | 'C' | 'G' | 'T' | 'N';
type AnnotationFormat = 'bed' | 'gff3' | 'gtf' | 'genePred' | 'genePredExt' | 'peaks' | 'narrowPeak' | 'broadPeak' | 'bigBed' | 'bedpe';


type HostedGenomes = "hs1" | "chm13v1.1" | "hg38" | "hg38_1kg" | "hg19" |
"hg18" | "mm39" | "mm10" | "mm9" | "rn7" |
Expand Down Expand Up @@ -788,6 +798,7 @@ interface CreateOptExtras {
export namespace BrowserEvents {
export type EventType = "trackremoved" | "trackdrag" | "trackdragend" | "locuschange" | "trackclick" | "trackorderchanged";

// returns the type of the event handler based on the event type
export type EventHandler<T extends EventType> =
T extends "trackremoved" ? (tracks: Tracks.Track[]) => EventReturn<T> :
T extends "locusChange" ? (loci: {
Expand All @@ -809,6 +820,7 @@ export namespace BrowserEvents {
}

export type CreateOpt = (GenomeOpt & CreateOptExtras) | (
// if a session URL is provided, we do not need a complete definition, additionally we should not allow a different genome
{
sessionURL: string;
} & Partial<CreateOptExtras>
Expand Down

0 comments on commit 45916a8

Please sign in to comment.