npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

typescript-checker

v2.0.0

Published

Powerful data validation library enabling type safety

Downloads

645

Readme

typescript-checker

Powerful data type validation library enabling type safety

Node.js CI

  • 🔒️ enables type safety on your TypeScript projects
  • ✔️ compatibility with TypeScript 3.x and 4.x
  • ✔️ supports exactOptionalPropertyTypes
  • 0️⃣️ zero dependencies
  • 🚀️ super small bundle size
  • 👵️ es5 compatibility
  • 🔀️ nodejs and browser ready

Examples

const validBody = {
	name: "Dodo",
	age: 42,
	meta: {
		canFly: false,
		pickupItems: ["egg", "grass"],
	},
}
const invalidBody = {
	name: null,
	age: 42,
	meta: [false, true],
}

const checkBody = Keys({
	name: And(TypeString, MinLength(2)),
	age: TypeNumber,
	meta: Keys({
		canFly: TypeBoolean,
		pickupItems: Items(OneOf("egg", "grass", "stone")),
	}),
})

const checkedValue = check(checkBody, validBody) // valid, returns value
const checkedValue = check(checkBody, invalidBody) // throws CheckerError

type IBody = CheckerSuccess<typeof checkBody>
// equals type/interface
type IBody = {
	name: string
	age: number
	meta: {
		canFly: boolean
		pickupItems: ("egg" | "grass" | "stone")[]
	}
}

// there are more ways to execute a checker and handle errors, see the detailed API docs!

Installation

Requires TypeScript 3.x or higher

npm i typescript-checker

OR

yarn add typescript-checker

API Docs

Checker

A Checker is a function to which a value can be passed, which should be validated. The Checker itself can be a base type checker like TypeString, a more complex checker with nested properties like Keys or Items, or a chain of different (logically connected) checker like And(TypeString, MinLength(2)) or And.then(Fallback(TypeString, () => "[]")).then(parseFilterQuery).

The Checker can be invoked like a function, returning a tuple [errors] | [null, validValue]. Either errors contains a list of errors, or validValue returns the validated and stripped value. Both cases can be conveniently checked via isCheckError(<result>) and isCheckValid(<result>). Alternatively, you can use the provided check(<checker>, <value>) function throwing a JavaScript Error, which can be try/catched.

const validBody = {
	name: "Dodo",
	age: 42,
	meta: {
		canFly: false,
		pickupItems: ["egg", "grass"],
	},
}
const invalidBody = {
	name: null,
	age: 42,
	meta: [false, true],
}

const checkBody = Keys({
	name: And(TypeString, MinLength(2)),
	age: TypeNumber,
	meta: Keys({
		canFly: TypeBoolean,
		pickupItems: Items(OneOf("egg", "grass", "stone")),
	}),
})

const checkResultValid = checkBody(validBody) // returns [null, validBody] indicating a valid checker result
const checkResultInvalid = checkBody(invalidBody) // returns [['.name expected string found null'], null] indicating an error checker result

if (isCheckValid(checkResultValid)) {
	// ...true
}

if (isCheckError(checkResultInvalid)) {
	// ...true
}

try {
	const value = check(checkBody, validBody)
	console.log(value) // outputs <value>
} catch (err) {
	console.error(err) // not executed
}

try {
	const value = check(checkBody, invalidBody)
	console.log(value) // not executed
} catch (err) {
	console.error(err) // err is CheckerError

	if (err instanceof CheckerError) {
		consoe.error(err.errors) // logs [['.name expected string found null']]
	}
}

JS/TS Types

  • TypeUndefined

  • TypeNull

  • TypeString

  • TypeBoolean

  • TypeNumber

  • TypeUnknown

  • TypeFunction

  • TypeObject

  • TypeArray

  • TypeEnum (typescript only)

    enum Status {
    	Pending,
    	Accepted,
    	Closed,
    }
    
    const checkStatus = TypeEnum(Status)
  • TypeEnumString (typescript only)

    enum Status {
    	Pending = "pending",
    	Accepted = "accepted",
    	Closed = "closed",
    }
    
    const checkStatus = TypeEnumString(Status)

Common Type Checker

  • TypeMatches (tests for a regex match)

  • TypeParseInt (string which holds an integer value, returns string)

  • TypeParseFloat (string which holds an integer value, returns string)

  • TypeParseBoolean (string which holds an boolean value, returns string)

  • TypeParseDate (string which holds an stringified Date value, returns string)

  • ConvertParseInt (string which holds an integer value, returns number)

  • ConvertParseFloat (string which holds a float value, returns number)

  • ConvertParseBoolean (string which holds a boolean value, returns boolean)

  • ConvertDate (string/number representing a valid Date, returns Date)

Checker Composition

  • And(checkerA, checkerB) like function composition (if checkerA succeeds, check the result using checkerB)

    And(TypeString, MinLength(10)) // string which is at least 10 characters long
  • Or(checkerA, checkerB, ...) check union type (if checkerA fails, check original value using checkerB)

    Or(TypeNull, TypeString) // accepts a string or null
    Or(TypeUndefined, TypeNumber) // accepts a number or undefined

Helper / Util Checker

  • Keys checks for an object containing certain keys with their corresponding value type

    const checkBody = Keys({
    	name: And(TypeString, MinLength(2)),
    	age: TypeNumber,
    })
    
    const checkNestedBody = Keys({
    	name: And(TypeString, MinLength(2)),
    	age: Or(TypeUndefined, TypeNumber), // property that must exist, but that may be undefined
    	meta: Keys({
    		canFly: TypeBoolean,
    	}),
    })
    
    const checkOptionalBody = Keys(
    	{
    		name: And(TypeString, MinLength(2)),
    		age: TypeNumber, // optional property
    	},
    	["age"],
    )
  • Items checks for an array containing certain item types

    Items(TypeString) // array containing only string values
    Items(TypeNumber) // array containing only number values
    Items(Keys({ name: And(TypeString, MinLength(2)) })) // array containing only objects with a <name> property of type string
    Items(Items(TypeNumber)) // 2d array containing only number values
    Items(Or(TypeString, TypeBoolean)) // array containing strings or booleans, e.g. ["hello world", false, true]
  • OneOf checks for defined literals

    OneOf(42, 1337) // only value 42 or 1337 is accepted
    OneOf("dodo", "raptor") // only value "dodo" or "raptor" is accepted
    OneOf("dodo", 42, false) // only value "dodo" or 42 or false is accepted
  • Fallback provides a way to take a default value if the checked value is undefined

    const myChecker = Fallback(TypeSting, () => "Take this as default value")
    
    const validResult = myChecker("Hello World") // validResult is "Hello World"
    const fallbackResult = myChecker(undefined) // fallbackResult is "Take this as default value"
  • Catch provides a way to take a default value if the checker fails

    const myChecker = Catch(TypeSting, () => "Take this as default value")
    
    const validResult = myChecker("Hello World") // validResult is "Hello World"
    const fallbackResult = myChecker(42) // fallbackResult is "Take this as default value"
  • withDefault is not a checker, but takes a checker result and a default value

    const myChecker = TypeSting
    
    const validResult = withDefault(myChecker("Hello World"), "Take this as default value") // validResult is "Hello World"
    const fallbackResult = withDefault(myChecker(42), "Take this as default value") // fallbackResult is "Take this as default value"
  • ConvertJSON takes a string value and tries to parse its JSON content

    const myJSONChecker = ConvertJSON
    
    const validObject = myJSONChecker('{"a": 42}') // validObject is {a: 42}
    const validArray = myJSONChecker("[1,2,3,4,5]") // validArray is [1,2,3,4,5]
    const invalidResult = myJSONChecker("dodo") // invalidResult holds an error
  • checkPair takes any value and checks if it's a pair with certain types

    const simplePairChecker = checkPair(TypeString, TypeNumber) // [string, number]
    
    simplePairChecker(["Hello", 42]) // valid
    simplePairChecker([42, "Hello"]) // error
    simplePairChecker(["Hello", 42, true]) // error, expects array of length 2
    
    // use a custom defined tuple type
    type CustomTuple = [string, boolean]
    const customTupleTypeChecker = checkPair<CustomTuple>(TypeString, TypeBoolean)

Validators

  • MinLength(number) checks if a given string has a minimum length
  • MaxLength(number) checks if a given string has a maximum length
  • Min(number) checks if a given number is greater-equals a certain value
  • Max(number) checks if a given number is less-equals a certain value
  • Between(number, number) checks if a given number is between two given values
  • IsUUID checks if a given string is a valid uuid (v1, v4, or v5)
  • EMail checks if a given string is a valid email address

Chaining

And provides an API for chaining checkers.

// Example 1

// takes a stringified JSON, parses it, and on success checks keys of the JSON object
const parseStringJSONPayload = And.then(TypeString)
	.then(ConvertJSON)
	.then(
		Keys({
			answer: TypeString,
			freeTextEnabled: TypeBoolean,
			next: TypeNumber,
		}),
	)

// Example 2

interface IFilterItem {
	key: string
	values: (string | number)[]
}

type FilterItems = IFilterItem[]

const checkFilterItem = Keys<IFilterItem>({ key: TypeString, values: Items(Or(TypeString, TypeNumber)) })
const checkFilterItems = Items(checkFilterItem)

const parseFilterQuery: Checker<string, FilterItems> = (value) => {
	try {
		const data = JSON.parse(value)
		const checkResult = checkFilterItems(data)

		return checkResult
	} catch (err) {
		return [err.message]
	}
}

// if value is undefined, we want to provide a fallback value before continue checking by parsing the
// stringified JSON by a custom checker
const checkPaginationFilter = And.then(Fallback(TypeString, () => "[]")).then(parseFilterQuery)

Custom Checker

By using the Checker<A,B> type you can build whatever checker you want.

// a custom Moment checker
const TypeMoment: Checker<unknown, Moment> = checkInstanceOf(Moment, "Moment")

// a custom checker for all uppercase letters
const checkAllUppercase: Checker<string, string> = (value) => {
	if (value === value.toUpperCase()) {
		return [null, value]
	} else {
		return [[`expected string with all uppercase letters, found ${value}`]]
	}
}

Type Inference

The type CheckerSuccess enables you to infer the type of a checker.

const checkBody = Keys({
	name: And(TypeString, MinLength(2)),
	age: TypeNumber,
	meta: Keys({
		canFly: TypeBoolean,
		pickupItems: Items(OneOf("egg", "grass", "stone")),
	}),
})

type IBody = CheckerSuccess<typeof checkBody>
// equals type/interface
type IBody = {
	name: string
	age: number
	meta: {
		canFly: boolean
		pickupItems: ("egg" | "grass" | "stone")[]
	}
}

Cast

If you prefer to declare your interfaces and types separately, you can just provide the type to the Cast checker adapter to make sure your checked types are equivalent. This also works for union types and optional members.

type Body = {
	name: string
	age: number
	meta: {
		canFly: boolean
		pickupItems: ("egg" | "grass" | "stone")[]
	}
}

// everything ok
const checkBody1 = Cast<Body>().as(
	Keys({
		name: And(TypeString, MinLength(2)),
		age: TypeNumber,
		meta: Keys({
			canFly: TypeBoolean,
			pickupItems: Items(OneOf("egg", "grass", "stone")),
		}),
	}),
	"same",
)

// compiler error -> key "meta" is missing
const checkBody2 = Cast<Body>().as(
	// @ts-expect-error
	Keys({
		name: And(TypeString, MinLength(2)),
		age: TypeNumber,
	}),
	"same",
)

type Egg = {
	kind: "egg"
	weight?: number
}

type Grass = {
	kind: "grass"
}

type Pickup = Egg | Grass

// compiler error -> key "weight" is missing
const checkSomeEgg1 = Cast<Egg>().as(
	Keys({
		kind: OneOf("egg"),
	}),
	// @ts-expect-error
	"same",
)

const checkSomeEgg2 = {} as Checker<unknown, Egg>

// compiler error -> missing Grass
const checkSomePickup = Cast<Pickup>().as(
	Or(checkSomeEgg2),
	// @ts-expect-error
	"same",
)

Pitfalls

When you manually override the generics of any checker constructor, the success type and the set of values that are accepted will differ. This will cause problems, when you use the success type to infer interface types. Therefore you should use Cast to create a checker adapter that only accepts checkers that symmetricaly matches the success type.

// ❗ never explicitly pass generics to any checker constructor
// it might work as expected (at first)
{
	type Body = {
		name: string
		age: number
		meta: {
			canFly: boolean
			pickupItems: ("egg" | "grass" | "stone")[]
		}
	}

	// no compiler error
	const checkBody1 = Keys<Body>({
		name: And(TypeString, MinLength(2)),
		age: TypeNumber,
		meta: Keys({
			canFly: TypeBoolean,
			pickupItems: Items(OneOf("egg", "grass", "stone")),
		}),
	})

	// compiler error -> key "meta" is missing in checker
	const checkBody2 = Keys<Body>({
		name: And(TypeString, MinLength(2)),
		age: TypeNumber,
	})
}

// it may fail badly when you least expect it
{
	type Egg = {
		type: "egg"
		color: "red" | "yellow"
		weight?: number
	}

	type Grass = {
		type: "grass"
	}

	type Pickup = Egg | Grass

	const checkSomeEgg = Keys<Egg>({
		type: OneOf("egg"),
		color: OneOf("red"), // 💥 missing "yellow"
		// 💥 missing weight (for typescript-checker < 2)
	})

	const checkSomePickup = Or<Pickup>(checkSomeEgg) // 💥 missing checkGrass
}

Roadmap / Todo

  • [ ] JS docs annotations
  • [ ] unit tests
  • [ ] convenient express integration
  • [ ] extend common checkers
  • [ ] convenient graphql integration

Contributors

  • Karl Kraus (@pyBlob) [Contributor & Library Founder]
  • Yannick Stachelscheid (@yss14) [Contributor & GitHub Moderator]
  • Martin Wepner (@martinwepner) [Contributor]
  • Simon Trieb (@strieb) [Contributor]
  • Tobias Klesel (@tobi12345) [Contributor]

License

This project is licensed under the MIT license.