\begin{figure}[H]
  \begin{lstlisting}[language=JavaScript]
export type InputType = {
  string: string,
  'date-time': Date,
  number: number,
  checkbox: boolean,
};

export type InputField<T = unknown> = {
  defaultValue?: T;
  validate?: (value: T) => boolean;
  label?: string;
  inputType: keyof InputType;
};

export class InputFieldBuilder {
  static from<K extends keyof InputType>(inputType: K): InputField<InputType[K]> {
    // ...
  }
  // ...
}

/**
 * Represents an action that can be performed on the target device
 **/
export abstract class Action<TInput, TOutput> {
  static module: string;
  static action: string;

  abstract run(params: TInput): Promise<TOutput>;
}

/**
* Returns the `Params` generic type in `Action<Params, Result>`
**/
export type ExtractActionParams<TAction extends Action<unknown, unknown>> =
  TAction extends Action<infer Params, unknown> ? Params : never;
\end{lstlisting}
  \caption{Exemple de types avancés en TypeScript (1/2)}
  \label{types}
\end{figure}
\begin{figure}[H]
\begin{lstlisting}[language=JavaScript]
/**
* This type returns a narrowed-down version of `Record<string, InputField>`, that requires that every value in `Params`
* has a field attached to it.
**/
export type MapActionParams<Params> = Params extends undefined | void | null
  ? unknown
  : {
      [Key in keyof Params]: Params[Key] extends InputType[keyof InputType]
        ? InputField<Params[Key]>
        : InputField;
    };

/**
* Conditionally sets the `fields` property of `Request` to:
* - if `TAction` is set to `unknown` (default), an optional `Record<string, InputField>`
* - if `TAction` is set to an `Action`, then it will extract the properties defined in that `Action`,
*   and require you to properly define their fields
**/
export type RequestFields<TAction> = TAction extends Action<unknown, unknown>
  ? {
      fields: Record<string, InputField> & MapActionParams<ExtractActionParams<TAction>>;
    }
  : {
      fields?: Record<string, InputField>;
    };

/**
 * Represents a network request that will be sent to the device to perform the similarly-named Action.
 **/
export type Request<TAction = unknown> = {
  action?: string;
  module?: string;
} & RequestFields<TAction>;

// Exemple:

class ShowMessageAction extends Action<{ message: string}, void> {
  run({message}): Promise<void> {
    // show `message`
  }
}

const ShowMessageRequestFields: RequestFields<ShowMessageAction> = {
  module: 'system',
  action: 'showMessage',
  fields: {
    // Si on omet `message` ou si on utilise un autre type, une erreur est affichée
    message: InputFieldBuilder.from('string').build()
  }
};
  \end{lstlisting}
  \caption{Exemple de types avancés en TypeScript (2/2)}
\end{figure}
