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

@coderspirit/nominal-typebox

v1.1.0

Published

Integration of @coderspirit/nominal with @sinclair/typebox

Downloads

119

Readme

@coderspirit/nominal-typebox

NPM version TypeScript License npm downloads Known Vulnerabilities Security Score

Nominal-Typebox brings nominal typing capabilities to Typebox schema definitions by leveraging Nominal.

Install instructions

Node

# With NPM
npm install @sinclair/typebox
npm install @coderspirit/nominal-typebox

# Or with PNPM
pnpm add @sinclair/typebox
pnpm add @coderspirit/nominal-typebox

# Or with Yarn:
yarn add @sinclair/typebox
yarn add @coderspirit/nominal-typebox

Usage instructions

TypeBox' Type.String -> brandedString

import type { FastBrand } from '@coderspirit/nominal'
import { brandedString } from '@coderspirit/nominal-typebox'

import { Object as TBObject } from '@sinclair/typebox'
import { TypeCompiler } from '@sinclair/typebox/compiler'

type Username = FastBrand<string, 'Username'>

// Use `brandedString` instead of Typebox' `Type.String`
const requestSchema = TBObject({
	// We can pass the same options Type.String has
	username: brandedString<'Username'>()
})
const requestValidator = TypeCompiler.Compile(requestSchema)

const requestObject = getRequestFromSomewhere() // unknown
if (!requestValidator.Check(requestObject)) {
	throw new Error('Invalid request!')
}

// At this point, the type checker knows that requestObject.username is
// "branded" as 'Username'

const username: Username = requestObject.username // OK
const corruptedUserame: Username = 'untagged string' // type error

TypeBox' Type.RegExp -> brandedRegExp


import type { FastBrand } from '@coderspirit/nominal'
import { brandedRegExp } from '@coderspirit/nominal-typebox'

import { Object as TBObject } from '@sinclair/typebox'
import { TypeCompiler } from '@sinclair/typebox/compiler'

type UserId = FastBrand<string, 'UserId'>

// Use `brandedString` instead of Typebox' `Type.String`
const requestSchema = TBObject({
	// We can pass the same options Type.String has
	userId: brandedRegExp<'UserId'>(
		/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
	)
})
const requestValidator = TypeCompiler.Compile(requestSchema)

const requestObject = getRequestFromSomewhere() // unknown
if (!requestValidator.Check(requestObject)) {
	throw new Error('Invalid request!')
}

// At this point, the type checker knows that requestObject.username is
// "branded" as 'Username'

const userId: UserId = requestObject.userId // OK
const corruptedUserId: UserId = 'untagged (and probably wrong) id' // type error

TypeBox' Type.Number -> brandedNumber

import type { FastBrand } from '@coderspirit/nominal'
import { brandedNumber } from '@coderspirit/nominal-typebox'

import { Object as TBObject } from '@sinclair/typebox'
import { TypeCompiler } from '@sinclair/typebox/compiler'

type Latitude = FastBrand<number, 'Latitude'>
type Longitude = FastBrand<number, 'Longitude'>

const requestSchema = TBObject({
	// We can pass the same options Type.Number has
	latitude: brandedNumber<'Latitude'>(),
	longitude: brandedNumber<'Longitude'>(),
})
const requestValidator = TypeCompiler.Compile(requestSchema)

const requestObject = getRequestFromSomewhere() // unknown
if (!requestValidator.Check(requestObject)) {
	throw new Error('Invalid request!')
}

const latitude: Latitude = requestObject.latitude // OK
const longitude: Longitude = requestObject.longitude // OK

const corruptedLat: Latitude = 10 // type error
const corruptedLon: Longitude = 10 // type error

TypeBox' Type.Integer -> brandedInteger

The same applies as for the two previous examples, you can use brandedInteger instead of Typebox' Type.Integer.


TypeBox' Type.Array -> brandedArray

brandedArray has the same signature as Typebox' Type.Array, except that we have to pass a "brand" string argument as its first parameter:

import { brandedArray } from '@coderspirit/nominal-typebox'
import { String as TBString } from '@sinclair/typebox'

const arraySchema = brandedArray(
	'MyArray',
	// Type.Array arguments:
	TBString(),
	{ minItems: 2 }
)

TypeBox' Type.Object -> brandedObject

brandedObject has the same signature as Typebox' Type.Object, except that we have to pass a "brand" string argument as its first parameter:

import { brandedObject } from '@coderspirit/nominal-typebox'
import { String as TBString } from '@sinclair/typebox'

const objectSchema = brandedObject(
	'MyObject',
	{
		a: TBstring(),
		b: TBString()
	},
	{ additionalProperties: true }
)

TypeBox' Type.Union -> brandedUnion

brandedUnion has the same signature as Typebox' Type.Union, except that we have to pass a "brand" string argument as its first parameter:

import { brandedUnion } from '@coderspirit/nominal-typebox'
import { Literal } from '@sinclair/typebox'

const unionSchema = brandedUnion(
	'State',
	[Literal('on'), Literal('off')]
)

Fallback alternative

In case this library does not provide a specific schema factory for your type, you can rely on brandedSchema. Notice that if you are using it for complex schemas, it can loose some branding information from inner/nested properties.

import type { FastBrand } from '@coderspirit/nominal'
import {
	brandedInteger,
	brandedSchema,
	brandedString,
} from '@coderspirit/nominal-typebox'
import { Record as TBRecord } from '@sinclair/typebox'

const personNameSchema = brandedString<'PersonName'>()
const personAgeSchema = brandedInteger<'PersonAge'>()
const recordSchema = brandedSchema('PeopleAges', TBRecord(
	personNameSchema,
	personAgeSchema,
))
const recordValidator = TypeCompiler.Compile(recordSchema)

const requestRecord = getRequestFromSomewhere() // unknown
if (!requestValidator.Check(requestRecord)) {
	throw new Error('Invalid request!')
}

// OK
const recordSink: FastBrand<Record<string, number>, 'PeopleAges'> =
	requestRecord

// @ts-expect-error Type Error!
const corruptedRecordSink: FastBrand<
	Record<string, number>, 'PeopleAges'
> = { Alice: 20, Bob: 30 }

// IMPORTANT!: Notice that `brandedSchema` is unable to preserve the
//             brands of keys & values in the record. This limitation
//             is due to the fact that `brandedSchema` is too generic.