-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Andres Correa Casablanca <[email protected]>
- Loading branch information
Showing
3 changed files
with
158 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
# @coderspirit/nominal-typebox | ||
|
||
[](https://www.npmjs.com/package/@coderspirit/nominal-typebox) | ||
[](http://www.typescriptlang.org/) | ||
[](https://opensource.org/licenses/MIT) | ||
[](https://www.npmjs.com/package/@coderspirit/nominal-typebox) | ||
[](https://snyk.io//test/github/Coder-Spirit/nominal?targetFile=@coderspirit/nominal-typebox/package.json) | ||
[](https://snyk.io/advisor/npm-package/@coderspirit/nominal-typebox) | ||
|
||
`Nominal-Typebox` brings [nominal typing](https://en.wikipedia.org/wiki/Nominal_type_system) | ||
capabilities to [Typebox](https://github.com/sinclairzx81/typebox) schema | ||
definitions by leveraging [Nominal](https://github.com/Coder-Spirit/nominal/blob/main/%40coderspirit/nominal/README.md). | ||
|
||
## 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 | ||
|
||
```typescript | ||
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.Number -> brandedNumber | ||
|
||
|
||
```typescript | ||
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: | ||
|
||
```typescript | ||
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: | ||
|
||
```typescript | ||
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: | ||
|
||
```typescript | ||
import { brandedUnion } from '@coderspirit/nominal-typebox' | ||
import { Literal } from '@sinclair/typebox' | ||
|
||
const unionSchema = brandedUnion( | ||
'State', | ||
[Literal('on'), Literal('off')] | ||
) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters