• Stars
    star
    7,817
  • Rank 4,838 (Top 0.1 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 1 year ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A 'CSS reset' for TypeScript, improving types for common JavaScript API's

ts-reset

TypeScript's built-in typings are not perfect. ts-reset makes them better.

Without ts-reset:

  • 🚨 .json (in fetch) and JSON.parse both return any
  • 🤦 .filter(Boolean) doesn't behave how you expect
  • 😡 array.includes often breaks on readonly arrays

ts-reset smooths over these hard edges, just like a CSS reset does in the browser.

With ts-reset:

  • 👍 .json (in fetch) and JSON.parse both return unknown
  • .filter(Boolean) behaves EXACTLY how you expect
  • 🥹 array.includes is widened to be more ergonomic
  • 🚀 And several more changes!

Example

// Import in a single file, then across your whole project...
import "@total-typescript/ts-reset";

// .filter just got smarter!
const filteredArray = [1, 2, undefined].filter(Boolean); // number[]

// Get rid of the any's in JSON.parse and fetch
const result = JSON.parse("{}"); // unknown

fetch("/")
  .then((res) => res.json())
  .then((json) => {
    console.log(json); // unknown
  });

Get Started

  1. Install: npm i -D @total-typescript/ts-reset

  2. Create a reset.d.ts file in your project with these contents:

// Do not add any other lines of code to this file!
import "@total-typescript/ts-reset";
  1. Enjoy improved typings across your entire project.

Installing only certain rules

By importing from @total-typescript/ts-reset, you're bundling all the recommended rules.

To only import the rules you want, you can import like so:

// Makes JSON.parse return unknown
import "@total-typescript/ts-reset/json-parse";

// Makes await fetch().then(res => res.json()) return unknown
import "@total-typescript/ts-reset/fetch";

For these imports to work, you'll need to ensure that, in your tsconfig.json, module is set to NodeNext or Node16.

Below is a full list of all the rules available.

Caveats

Use ts-reset in applications, not libraries

ts-reset is designed to be used in application code, not library code. Each rule you include will make changes to the global scope. That means that, simply by importing your library, your user will be unknowingly opting in to ts-reset.

Rules

Make JSON.parse return unknown

import "@total-typescript/ts-reset/json-parse";

JSON.parse returning any can cause nasty, subtle bugs. Frankly, any any's can cause bugs because they disable typechecking on the values they describe.

// BEFORE
const result = JSON.parse("{}"); // any

By changing the result of JSON.parse to unknown, we're now forced to either validate the unknown to ensure it's the correct type (perhaps using zod), or cast it with as.

// AFTER
import "@total-typescript/ts-reset/json-parse";

const result = JSON.parse("{}"); // unknown

Make .json() return unknown

import "@total-typescript/ts-reset/fetch";

Just like JSON.parse, .json() returning any introduces unwanted any's into your application code.

// BEFORE
fetch("/")
  .then((res) => res.json())
  .then((json) => {
    console.log(json); // any
  });

By forcing res.json to return unknown, we're encouraged to distrust its results, making us more likely to validate the results of fetch.

// AFTER
import "@total-typescript/ts-reset/fetch";

fetch("/")
  .then((res) => res.json())
  .then((json) => {
    console.log(json); // unknown
  });

Make .filter(Boolean) filter out falsy values

import "@total-typescript/ts-reset/filter-boolean";

The default behaviour of .filter can feel pretty frustrating. Given the code below:

// BEFORE
const filteredArray = [1, 2, undefined].filter(Boolean); // (number | undefined)[]

It feels natural that TypeScript should understand that you've filtered out the undefined from filteredArray. You can make this work, but you need to mark it as a type predicate:

const filteredArray = [1, 2, undefined].filter((item): item is number => {
  return !!item;
}); // number[]

Using .filter(Boolean) is a really common shorthand for this. So, this rule makes it so .filter(Boolean) acts like a type predicate on the array passed in, removing any falsy values from the array member.

// AFTER
import "@total-typescript/ts-reset/filter-boolean";

const filteredArray = [1, 2, undefined].filter(Boolean); // number[]

Make .includes on as const arrays less strict

import "@total-typescript/ts-reset/array-includes";

This rule improves on TypeScript's default .includes behaviour. Without this rule enabled, the argument passed to .includes MUST be a member of the array it's being tested against.

// BEFORE
const users = ["matt", "sofia", "waqas"] as const;

// Argument of type '"bryan"' is not assignable to
// parameter of type '"matt" | "sofia" | "waqas"'.
users.includes("bryan");

This can often feel extremely awkward. But with the rule enabled, .includes now takes a widened version of the literals in the const array.

// AFTER
import "@total-typescript/ts-reset/array-includes";

const users = ["matt", "sofia", "waqas"] as const;

// .includes now takes a string as the first parameter
users.includes("bryan");

This means you can test non-members of the array safely.

Make .indexOf on as const arrays less strict

import "@total-typescript/ts-reset/array-index-of";

Exactly the same behaviour of .includes (explained above), but for .lastIndexOf and .indexOf.

Make Set.has() less strict

import "@total-typescript/ts-reset/set-has";

Similar to .includes, Set.has() doesn't let you pass members that don't exist in the set:

// BEFORE
const userSet = new Set(["matt", "sofia", "waqas"] as const);

// Argument of type '"bryan"' is not assignable to
// parameter of type '"matt" | "sofia" | "waqas"'.
userSet.has("bryan");

With the rule enabled, Set is much smarter:

// AFTER
import "@total-typescript/ts-reset/set-has";

const userSet = new Set(["matt", "sofia", "waqas"] as const);

// .has now takes a string as the argument!
userSet.has("bryan");

Make Map.has() less strict

import "@total-typescript/ts-reset/map-has";

Similar to .includes or Set.has(), Map.has() doesn't let you pass members that don't exist in the map's keys:

// BEFORE
const userMap = new Map([
  ["matt", 0],
  ["sofia", 1],
  [2, "waqas"],
] as const);

// Argument of type '"bryan"' is not assignable to
// parameter of type '"matt" | "sofia" | "waqas"'.
userMap.has("bryan");

With the rule enabled, Map follows the same semantics as Set.

// AFTER
import "@total-typescript/ts-reset/map-has";

const userMap = new Map([
  ["matt", 0],
  ["sofia", 1],
  [2, "waqas"],
] as const);

// .has now takes a string as the argument!
userMap.has("bryan");

Removing any[] from Array.isArray()

import "@total-typescript/ts-reset/is-array";

When you're using Array.isArray, you can introduce subtle any's into your app's code.

// BEFORE

const validate = (input: unknown) => {
  if (Array.isArray(input)) {
    console.log(input); // any[]
  }
};

With is-array enabled, this check will now mark the value as unknown[]:

// AFTER
import "@total-typescript/ts-reset/is-array";

const validate = (input: unknown) => {
  if (Array.isArray(input)) {
    console.log(input); // unknown[]
  }
};

Rules we won't add

Object.keys/Object.entries

A common ask is to provide 'better' typings for Object.keys, so that it returns Array<keyof T> instead of Array<string>. Same for Object.entries. ts-reset won't be including rules to change this.

TypeScript is a structural typing system. One of the effects of this is that TypeScript can't always guarantee that your object types don't contain excess properties:

type Func = () => {
  id: string;
};

const func: Func = () => {
  return {
    id: "123",
    // No error on an excess property!
    name: "Hello!",
  };
};

So, the only reasonable type for Object.keys to return is Array<string>.

Generics for JSON.parse, Response.json etc

A common request is for ts-reset to add type arguments to functions like JSON.parse:

const str = JSON.parse<string>('"hello"');

console.log(str); // string

This appears to improve the DX by giving you autocomplete on the thing that gets returned from JSON.parse.

However, we argue that this is a lie to the compiler and so, unsafe.

JSON.parse and fetch represent validation boundaries - places where unknown data can enter your application code.

If you really know what data is coming back from a JSON.parse, then an as assertion feels like the right call:

const str = JSON.parse('"hello"') as string;

console.log(str); // string

This provides the types you intend and also signals to the developer that this is slightly unsafe.

More Repositories

1

ts-error-translator

VSCode extension to turn TypeScript errors into plain English
TypeScript
2,360
star
2

xstate-catalogue

Professionally designed, interactive state machines
TypeScript
813
star
3

zod-fetch

Simple function for building a type-safe fetcher with Zod
TypeScript
664
star
4

xstate-codegen

A codegen tool for 100% TS type-safety in XState
TypeScript
244
star
5

total-typescript-monorepo

The home of all Matt's internal tooling
TypeScript
196
star
6

sextant

A development tool to chart application flows, then implement them in code
TypeScript
168
star
7

pkg-demo

TypeScript
166
star
8

xstate-next-boilerplate

A performant, robust starting point for any React application
TypeScript
130
star
9

graph-docs-cli

Revolutionise your docs using knowledge graphs built from Markdown
TypeScript
95
star
10

redux-xstate-poc

Manage your Redux side effects with XState. Use 100% of XState's features.
TypeScript
89
star
11

package-tools

76
star
12

typescript-node

TypeScript
55
star
13

esbuild-node

TypeScript
44
star
14

boilersuit

A super-powered generator for selectors, reducers, actions, constants and sagas in react-boilerplate
JavaScript
28
star
15

turborepo-stately-demo

JavaScript
27
star
16

tsconfig-creator

TypeScript
27
star
17

use-fsm-reducer

useReducer for state machine enthusiasts
TypeScript
26
star
18

make-route-map

Type-safe routing for single-page-applications
TypeScript
24
star
19

xstate-sync-to-context

TypeScript
24
star
20

xstate-test-playwright

TypeScript
22
star
21

total-typescript-monorepo-template

TypeScript
21
star
22

matt-cli

TypeScript
20
star
23

debates-stream

TypeScript
20
star
24

tt-package-demo

TypeScript
20
star
25

workshops

TypeScript
14
star
26

xstate-real-world-app

TypeScript
13
star
27

figma-xstate-plugin

TypeScript
11
star
28

xstate-stream-demo-app

A full-stack app built with Next.js, XState, Typescript and GraphQL.
TypeScript
11
star
29

practice-cli

JavaScript
8
star
30

my-monorepo

JavaScript
7
star
31

react-finland-talk

TypeScript
7
star
32

total-typescript-cli

TypeScript
7
star
33

create-t3-turbo-test

TypeScript
6
star
34

neverthrow-tutorial

TypeScript
6
star
35

pro-essentials-cut-down-to-chapters

TypeScript
6
star
36

xstate-sandpack-tutorials

TypeScript
5
star
37

total-typescript-helpers

TypeScript
5
star
38

as-prop-perf-demo

TypeScript
5
star
39

react-day-berlin-react-types

TypeScript
5
star
40

mattpocock

5
star
41

full-stack-ui

TypeScript
5
star
42

xstate-command-palette

TypeScript
4
star
43

matt-product-boilerplate

TypeScript
4
star
44

graphql-rest-contracts

Use graphql as a schema language to define a REST API
JavaScript
4
star
45

pro-essentials-cut-down

TypeScript
4
star
46

vscode-day-talk

TypeScript
3
star
47

project-references-demo

TypeScript
3
star
48

react-typescript-tutorial-05

TypeScript
3
star
49

video-ideas

TypeScript
3
star
50

gql-tada-testing

TypeScript
3
star
51

obs-stinger

TypeScript
2
star
52

voicehacker

JavaScript
2
star
53

wordpress-react-simplified

The simplest imaginable implementation of React with Wordpress. No external dependencies, just the PHP passing the page ID to the javascript, which goes and gets the correct page info and renders it.
JavaScript
2
star
54

cypress-xstate-demo

TypeScript
2
star
55

cypress-job-search

A simple web scraper built with Cypress and json-server
JavaScript
1
star
56

xstate-form-test

TypeScript
1
star
57

kelvin-chat

TypeScript
1
star
58

vscode-xstate

1
star
59

multi-step-form

TypeScript
1
star
60

xstate-inspect-storybook-example

JavaScript
1
star
61

react-typescript-boilerplate

JavaScript
1
star
62

turbo-ignore-demo

JavaScript
1
star