Useful TypeScript Helpers

September 7, 2023 Updated: April 29, 2024

Useful TypeScript Helpers

Table of Contents

Disclaimer

Type Declarations

View Transition

The startViewTransition function is a method on the document object which enables view transitions. The following types are based on the documentation at MDN. Since the API is currently experimental, you may need to add the following type to your type declaration file to avoid errors.

Using in SvelteKit

In SvelteKit, you can add the above type declaration to a file in the src directory, such as src/types.d.ts. Then you can add view transitions to your SvelteKit app by following the video below, ignoring the @ts-ignore comments.

Environment Variables

Environment variables can be declared in the global namespace to provide type information for import.meta.env and process.env. The following example demonstrates how to declare environment variables in TypeScript.

ts
declare global {
	interface ImportMetaEnv {
		MY_ENV: string;
	}
}

import.meta.env.MY_ENV; // string

declare global {
	namespace NodeJS {
		interface ProcessEnv {
			MY_ENV: string;
		}
	}
}

process.env.MY_ENV; // string

Keep in mind that this is type faith, not type safe. You can also use a schema validation library to parse your environment variables and infer the type from that. If a variable is missing, you can throw an error.

Utility Types

Combine/Prettify Two Object Types

The Prettify type is a simple type which takes an object type with intersections and combines them to make them easier to read. The Combine type is a simple type which takes 2 object types and combines them into one.

ts
type Prettify<T> = {
	[K in keyof T]: T[K];
} & {};

type Combine<T1, T2> = Prettify<
	{
		[K in keyof (T1 | T2)]: T1[K] | T2[K];
	} & Partial<T1 & T2>
>;

Prettifying Nested Objects

Going even further, you can use the PrettifyNested type to prettify nested object types. The Record<PropertyKey, unknown> type extends basic objects, but not classes like Date, Map, Set, etc.

ts
type PrettifyNested<T> = {
  [K in keyof T]: T[K] extends Record<PropertyKey, unknown> | unknown[] ? PrettifyNested<T[K]> : T[K];
} & {};

Numeric Range

The NumericRange type is a utility type that takes a start and end number and returns a union of all numbers between the two.

ts
type LessThan<TNumber extends number, TArray extends any[] = []> = TNumber extends TArray["length"]
	? TArray[number]
	: LessThan<TNumber, [...TArray, TArray["length"]]>;

type NumericRange<TStart extends number, TEnd extends number> =
	| Exclude<TEnd, LessThan<TStart>>
	| Exclude<LessThan<TEnd>, LessThan<TStart>>;

Type Guards

IsAny

There are a few ways to check if a type if any. One example is to use an interection. As explained in the source link below:

The type constraint 0 extends 1 is not satisfied (0 is not assignable to 1). Therefore, it should be impossible for 0 extends (1 & T) to be satisfied either, since (1 & T) should be even narrower than 1. However, when T is any, it reduces to 0 extends any, which is satisfied. That’s because any is intentionally unsound and acts as both a supertype and subtype of almost every other type (except for never).

ts
type IsAny<T> = 0 extends (1 & T) ? true : false;

Another method is to use the fact that it’s both a subtype and supertype of almost every other type by extending never. When any extends never, it results in a union of true | false or boolean. boolean extends boolean, but not true or false individually.

ts
type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;

Satisfies

The Satisfies type is a utility type that checks if a type satisfies an expected type and returns the actual type.

ts
type Primitive = undefined | null | boolean | number | bigint | string;
type PrimitiveObject = Record<string, Primitive>;

type Satisfies<Expected, Actual extends Expected> = Actual;

type ComplexObject = {
		foo: string
		bar: Record<string, string>
}

type SimpleObject = Satisfies<
		PrimitiveObject,
		Pick<ComplexObject, "foo">
>;

type BadSimpleObject = Satisfies<
		PrimitiveObject,
		Pick<ComplexObject, "foo" | "bar"> // Property 'bar' is incompatible with index signature.
>;