New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@coderspirit/nominal

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@coderspirit/nominal - npm Package Compare versions

Comparing version 2.1.0 to 3.0.0

dist/cjs/Brands.d.ts

11

dist/cjs/index.d.ts

@@ -1,9 +0,2 @@

export type { WithTag } from './WithTag';
export type { WithTags } from './WithTags';
export type { WithoutTag } from './WithoutTag';
export type { WithoutTags } from './WithoutTags';
export type { NegateTag } from './NegateTag';
export type { GenericTainted } from './GenericTainted';
export type { GenericUntainted } from './GenericUntainted';
export type { Tainted } from './Tainted';
export type { Untainted } from './Untainted';
export type { KeepProperties, KeepPropertyIfValueMatches, PropertyTypeDefinition, WithStrictProperty, WithProperty, } from './Properties';
export { WithBrand, WithFlavor, WithoutBrand } from './Brands';

@@ -1,17 +0,44 @@

import { __BaseType, __Brand, __TypeTags } from './Symbols';
export declare type BrandMarker<BaseType, Brand extends string | symbol> = {
import { __BaseType, __Brand, __Properties } from './Symbols';
export declare type PropertyKeyType = string | symbol;
export declare type PropertyValueType = string | number | boolean | symbol;
/**
* It encapsulates specific property key-value metadata that will be attached
* to other types.
*/
export declare type PropertyWrapper<PropertyKey extends PropertyKeyType, PropertyValue extends PropertyValueType> = {
readonly [key in PropertyKey]: PropertyValue;
};
/**
* It helps us to construct new types with added properties "metadata".
*/
export declare type PropertiesMarker<BaseType, Properties extends PropertyWrapper<PropertyKeyType, PropertyValueType>> = BaseType & {
readonly [__BaseType]: BaseType;
readonly [__Properties]: Properties;
};
export declare type BrandType = string | symbol;
/**
* It helps us to construct new branded types.
*/
export declare type BrandMarker<BaseType, Brand extends BrandType> = BaseType & {
readonly [__BaseType]: BaseType;
readonly [__Brand]: Brand;
};
export declare type FlavorMarker<BaseType, Flavor extends string | symbol> = {
/**
* It helps us to construct new flavored types.
*/
export declare type FlavorMarker<BaseType, Brand extends BrandType> = BaseType & {
readonly [__BaseType]?: BaseType;
readonly [__Brand]?: Flavor;
readonly [__Brand]?: Brand;
};
export declare type TagsMarker<BaseType, TypeTags> = {
/**
* Useful for type inference purposes
*/
export declare type BaseTypeMarker<BaseType> = BaseType & {
readonly [__BaseType]: BaseType;
readonly [__TypeTags]: TypeTags;
};
export declare type OptionalTagsMarker<BaseType, TypeTags> = {
/**
* Useful for type inference purposes
*/
export declare type WeakBaseTypeMarker<BaseType> = BaseType & {
readonly [__BaseType]?: BaseType;
readonly [__TypeTags]?: TypeTags;
};

@@ -1,6 +0,8 @@

declare const TaintSymbol: unique symbol;
export declare type TaintSymbolType = typeof TaintSymbol;
export declare const __BaseType: unique symbol;
export declare const __TypeTags: unique symbol;
export declare const __Properties: unique symbol;
export declare const __Brand: unique symbol;
export declare const __PropertyName: unique symbol;
export declare const __PropertyValues: unique symbol;
declare const __ImpossibleSymbol: unique symbol;
export declare type __Impossible = typeof __ImpossibleSymbol;
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__Brand = exports.__TypeTags = exports.__BaseType = void 0;
const TaintSymbol = Symbol('TaintSymbol');
exports.__PropertyValues = exports.__PropertyName = exports.__Brand = exports.__Properties = exports.__BaseType = void 0;
exports.__BaseType = Symbol('__BaseType');
exports.__TypeTags = Symbol('__TypeTags');
exports.__Properties = Symbol('__Properties');
exports.__Brand = Symbol('__Brand');
exports.__PropertyName = Symbol('__PropertyName');
exports.__PropertyValues = Symbol('__PropertyValues');
// Some Nominal tricks rely on the fact that this symbol and its type won't be
// exported as public to the library's users.
const __ImpossibleSymbol = Symbol('__Impossible');

@@ -1,9 +0,2 @@

export type { WithTag } from './WithTag';
export type { WithTags } from './WithTags';
export type { WithoutTag } from './WithoutTag';
export type { WithoutTags } from './WithoutTags';
export type { NegateTag } from './NegateTag';
export type { GenericTainted } from './GenericTainted';
export type { GenericUntainted } from './GenericUntainted';
export type { Tainted } from './Tainted';
export type { Untainted } from './Untainted';
export type { KeepProperties, KeepPropertyIfValueMatches, PropertyTypeDefinition, WithStrictProperty, WithProperty, } from './Properties';
export { WithBrand, WithFlavor, WithoutBrand } from './Brands';

@@ -1,17 +0,44 @@

import { __BaseType, __Brand, __TypeTags } from './Symbols';
export declare type BrandMarker<BaseType, Brand extends string | symbol> = {
import { __BaseType, __Brand, __Properties } from './Symbols';
export declare type PropertyKeyType = string | symbol;
export declare type PropertyValueType = string | number | boolean | symbol;
/**
* It encapsulates specific property key-value metadata that will be attached
* to other types.
*/
export declare type PropertyWrapper<PropertyKey extends PropertyKeyType, PropertyValue extends PropertyValueType> = {
readonly [key in PropertyKey]: PropertyValue;
};
/**
* It helps us to construct new types with added properties "metadata".
*/
export declare type PropertiesMarker<BaseType, Properties extends PropertyWrapper<PropertyKeyType, PropertyValueType>> = BaseType & {
readonly [__BaseType]: BaseType;
readonly [__Properties]: Properties;
};
export declare type BrandType = string | symbol;
/**
* It helps us to construct new branded types.
*/
export declare type BrandMarker<BaseType, Brand extends BrandType> = BaseType & {
readonly [__BaseType]: BaseType;
readonly [__Brand]: Brand;
};
export declare type FlavorMarker<BaseType, Flavor extends string | symbol> = {
/**
* It helps us to construct new flavored types.
*/
export declare type FlavorMarker<BaseType, Brand extends BrandType> = BaseType & {
readonly [__BaseType]?: BaseType;
readonly [__Brand]?: Flavor;
readonly [__Brand]?: Brand;
};
export declare type TagsMarker<BaseType, TypeTags> = {
/**
* Useful for type inference purposes
*/
export declare type BaseTypeMarker<BaseType> = BaseType & {
readonly [__BaseType]: BaseType;
readonly [__TypeTags]: TypeTags;
};
export declare type OptionalTagsMarker<BaseType, TypeTags> = {
/**
* Useful for type inference purposes
*/
export declare type WeakBaseTypeMarker<BaseType> = BaseType & {
readonly [__BaseType]?: BaseType;
readonly [__TypeTags]?: TypeTags;
};

@@ -1,1 +0,1 @@

import { __BaseType, __Brand, __TypeTags } from './Symbols.js';
import { __BaseType, __Brand, __Properties } from './Symbols.js';

@@ -1,6 +0,8 @@

declare const TaintSymbol: unique symbol;
export declare type TaintSymbolType = typeof TaintSymbol;
export declare const __BaseType: unique symbol;
export declare const __TypeTags: unique symbol;
export declare const __Properties: unique symbol;
export declare const __Brand: unique symbol;
export declare const __PropertyName: unique symbol;
export declare const __PropertyValues: unique symbol;
declare const __ImpossibleSymbol: unique symbol;
export declare type __Impossible = typeof __ImpossibleSymbol;
export {};

@@ -1,4 +0,8 @@

const TaintSymbol = Symbol('TaintSymbol');
export const __BaseType = Symbol('__BaseType');
export const __TypeTags = Symbol('__TypeTags');
export const __Properties = Symbol('__Properties');
export const __Brand = Symbol('__Brand');
export const __PropertyName = Symbol('__PropertyName');
export const __PropertyValues = Symbol('__PropertyValues');
// Some Nominal tricks rely on the fact that this symbol and its type won't be
// exported as public to the library's users.
const __ImpossibleSymbol = Symbol('__Impossible');
{
"name": "@coderspirit/nominal",
"version": "2.1.0",
"version": "3.0.0",
"description": "Powerful nominal types for your project",

@@ -5,0 +5,0 @@ "main": "./dist/cjs/index.js",

@@ -17,15 +17,7 @@ # @coderspirit/nominal

checker.
- **Tags:** As you might imagine, *tags* allow us to "attach" multiple nominal
types to a same variable. They are very useful to express things like:
- *roles & capabilities*: some times interfaces & classes are not enough, we
might need or want to encode many roles and/or capabilities at the same
time for a single entity, and to use the type checker to enforce
constraints based on that information.
- *logical/mathematical properties*: each attached *tag* can be interpreted
as, in some way, a property assertion (for example we could attach
properties like *positive*, *odd* or *prime* to a number, at the same time).
- **Properties:** They are very useful to express things like logical and
mathematical properties.
On top of these three kinds of nominal type, `Nominal` also offers
[taint tracking](https://en.wikipedia.org/wiki/Taint_checking) capabilities with
zero runtime overhead.
While each type can only have either a *brand* or a *flavor*, we can easily
combine *brands* or *flavors* with *properties*.

@@ -54,19 +46,15 @@ ## Install instructions

Pending explanation.
## Flavors
```typescript
import { WithBrand } from '@coderspirit/nominal'
Pending explanation.
type Email = WithBrand<string, 'Email'>
type Username = WithBrand<string, 'Username'>
## Tags
const email: Email = 'admin@acme.com' as Email // Ok
const user: Username = 'admin' as Username // Ok
const text: string = email // OK
const anotherText: string = user // Ok
### Basic tags
To define a new tagged type based on a previous type, we can do:
```typescript
import { WithTag } from '@coderspirit/nominal'
type Prime = WithTag<number, 'Prime'>
const myPrime: Prime = 23 as Prime
const eMail: Email = 'admin@acme.com' // Error, as we don't have a cast here
const mail: Email = user // Error, as the brands don't match
```

@@ -79,239 +67,139 @@

- One way to protect against other developers "forging" the type is to use
symbols instead of strings as "type tags" when defining the new nominal type.
symbols instead of strings as property keys or property values when defining
the new nominal type.
### Combining tags
## Flavors
`WithTag` has been implemented in a way that allows us to easily compose many
nominal types for a same value:
```typescript
import { WithTag, WithTags } from '@coderspirit/nominal'
import { WithFlavor } from '@coderspirit/nominal'
type Integer = WithTag<number, 'Integer'>
type Even = WithTag<number, 'Even'>
type Positive = WithTag<number, 'Positive'>
type Email = WithFlavor<string, 'Email'>
type Username = WithFlavor<string, 'Username'>
// The first way is by adding "tags" to an already "tagged" type
type EvenInteger = WithTag<Integer, 'Even'>
const email: Email = 'admin@acme.com' as Email // Ok
const user: Username = 'admin' as Username // Ok
const text: string = email // OK
const anotherText: string = user // Ok
// The second way is by adding many "tags" at the same time
type EvenPositive = WithTags<number, ['Even', 'Positive']>
const eMail: Email = 'admin@acme.com' // Ok, flavors are more flexible than brands
const mail: Email = user // Error, as the flavors don't match
```
#### **Interesting properties**
- `WithTag` and `WithTags` are additive, commutative and idempotent.
- The previous point means that we don't have to worry about the order of
composition, we won't suffer typing inconsistencies because of that.
#### **Advice**
- Although we perform a "static cast" here, this should be done only when:
- the value is a literal (as in the example)
- in validation, sanitization and/or anticorruption layers.
- One way to protect against other developers "forging" the type is to use
symbols instead of strings as property keys or property values when defining
the new nominal type.
#### **Unused type tags can be preserved across function boundaries**
This feature can be very useful when we need to verify many properties for the
same value and we don't want to lose this information along the way as the value
is passed from one function to another.
## Properties
```typescript
function throwIfNotEven<T extends number>(v: T): WithTag<T, 'Even'> {
if (v % 2 == 1) throw new Error('Not Even!')
return v as WithTag<T, 'Even'>
}
### Introduction
function throwIfNotPositive<T extends number>(v: T): WithTag<T, 'Positive'> {
if (v <= 0) throw new Error('Not positive!')
return v as WithTag<T, 'Positive'>
}
To define a new type with a property, we can do:
const v1 = 42
// typeof v2 === WithTag<number, 'Even'>
const v2 = throwIfNotEven(v1)
// typeof v3 === WithTags<number, ['Even', 'Positive']>
const v3 = throwIfNotPositive(v2)
```
### Removing tags
If needed, we can easily remove *tags* from our types:
```typescript
import { WithTag, WithoutTag } from '@coderspirit/nominal'
type Email = WithTag<string, 'Email'>
// NotEmail === string
type NotEmail = WithoutTag<string, 'Email'>
// NotAnEmailAnymore === string
type NotAnEmailAnymore = WithoutTag<Email, 'Email'>
import { WithProperty } from '@coderspirit/nominal'
type Even = WithProperty<number, 'Parity', 'Even'>
const myEven: Even = 42 as Even
```
The tags that we do not explicitly remove are preserved:
```typescript
type FibonacciPrime = WithTags<number, ['Prime', 'Fibonacci']>
type Fibonacci = WithoutTag<FibonacciPrime, 'Prime'>
```
If we want to use the properties as simple tags, we can omit the property value,
and it will implicitly default to `true`, although it's less flexible:
WARNING: Notice that it's not a good idea to preserve all the previous tags for
return types when the passed value is transformed. For example:
```typescript
function square<T extends number>(v: T): WithoutTag<T, 'Prime'> {
return v * v as WithoutTag<T, 'Prime'>
}
const myNumber: FibonacciPrime = 13 as FibonacciPrime
// Notice that the return type's tag is wrong, as 169 is not a Fibonacci number
// typeof mySquaredNumber === WithTag<number, 'Fibonacci'>
// mySquaredNumber === 13*13 === 169
const mySquaredNumber = square(myNumber)
import { WithProperty } from '@coderspirit/nominal'
type Positive = WithProperty<number, 'Positive'>
const myPositive: Positive = 1 as Positive
```
We can also remove many tags at once, or all of them:
```typescript
// Editor === WithTag<User, 'Editor'>
type Editor = WithoutTags<
WithTags<User, ['Editor', 'Moderator', 'Admin']>,
['Moderator', 'Admin']
>
#### **Advice**
- Although we perform a "static cast" here, this should be done only when:
- the value is a literal (as in the example)
- in validation, sanitization and/or anticorruption layers.
- One way to protect against other developers "forging" the type is to use
symbols instead of strings as property keys or property values when defining
the new nominal type.
// NewNumber === number
type NewNumber = WithoutTags<FibonacciPrime>
```
#### **Interesting properties**
- `WithProperty` is additive, commutative and idempotent.
- The previous point means that we don't have to worry about the order of
composition, we won't suffer typing inconsistencies because of that.
### Negating tags
### Crazy-level strictness
If needed, we can easily **negate** *tags* from our types. This is similar to
removing them, but it allows us to reject some values with certain tags.
If we want, we can even define "property types", to ensure that we don't set
invalid values:
```typescript
import { WithTag, NegateTag } from '@coderspirit/nominal'
import { PropertyTypeDefinition, WithStrictProperty } from '@coderspirit/nominal'
type Parity = PropertyTypeDefinition<'Parity', 'Even' | 'Odd'>
type Email = WithTag<string, 'Email'>
type NegatedEmail = NegateTag<string, 'Email'>
type NegatedEmail2 = NegateTag<Email, 'Email'>
// == WithProperty<number, 'Parity', 'Even'>
type Even = WithStrictProperty<number, Parity, 'Even'>
const email: Email = 'coyote@ac.me' as Email
// The type checked accepts this
const untypedEmail: string = email
// The type checker will error with any of the following two lines
const notEmail1: NegatedEmail = email // ERROR!
const notEmail2: NegatedEmail2 = email // ERROR!
// NotEmail & NotAnEmailAnymore are still compatible with string
const notEmail3: NegatedEmail = 'not an email'
const notEmail4: string = notEmail3 // This is OK :)
const notEmail5: NegatedEmail2 = 'not an email anymore'
const notEmail6: string = notEmail5 // This is also OK :)
// == never
type Wrong = WithStrictProperty<number, Parity, 'Seven'>
```
This can be a powerful building block to implement values tainting, although we
already provide an out-of-box solution for that.
### Advanced use cases
Now that we know how to [add](#basic-tags), [remove](#removing-tags), and
[negate](#negating-tags) tags, let's see a fancy example:
#### **Properties can be preserved across function boundaries**
This feature can be very useful when we need to verify many properties for the
same value and we don't want to lose this information along the way as the value
is passed from one function to another.
```typescript
// By combining tags & tag negations we can define types that allow us to
// express logical or mathematical properties in a consistent way.
function throwIfNotEven<T extends number>(v: T): WithProperty<T, 'Parity', 'Even'> {
if (v % 2 == 1) throw new Error('Not Even!')
return v as WithProperty<T, 'Even'>
}
// Now we can use `Even` and `Odd` without fearing that they will be used at the
// same time for the same variable.
type Even<N extends number = number> = NegateTag<WithTag<N, 'Even'>, 'Odd'>
type Odd<N extends number = number> = NegateTag<WithTag<N, 'Odd'>, 'Even'>
type ChangeParity<N extends Even | Odd> = N extends Even ? Odd : Even
type Positive<N extends number = number> = NegateTag<WithTag<N, 'Positive'>, 'Negative'>
type Negative<N extends number = number> = NegateTag<WithTag<N, 'Negative'>, 'Positive'>
// We preserve sign when the number is positive for obvious reasons, but we
// cannot do the same for negative values (for example for the value -0.5).
type PlusOneResult<N extends Even | Odd> = N extends Positive
? Positive<ChangeParity<N>> // Notice that we do not write Positive & ChangeParity<N>
: ChangeParity<N>
function <N extends Even | Odd>plusOne(v: N): PlusOneResult<N> {
return v + 1 as PlusOneResult<N>
function throwIfNotPositive<T extends number>(v: T): WithProperty<T, 'Sign', 'Positive'> {
if (v <= 0) throw new Error('Not positive!')
return v as WithProperty<T, 'Positive'>
}
const positiveEven: Positive<Even> = 42 as Positive<Even>
const positiveOdd: Positive<Odd> = 3 as Positive<Odd>
const v1 = 42
// typeof positiveEvenPlus1 == Positive<Odd>
const positiveEvenPlus1 = plusOne(positiveEven)
// typeof v2 === WithProperty<number, 'Parity', 'Even'>
const v2 = throwIfNotEven(v1)
// typeof positiveOddPlus1 === Positive<Even>
const positiveOddPlus1 = plusOne(positiveOdd)
// typeof v3 extends WithProperty<number, 'Parity', 'Even'>
// typeof v3 extends WithProperty<number, 'Sign', 'Positive'>
const v3 = throwIfNotPositive(v2)
```
## Tainting
#### Chosing what properties to preserve across function boundaries
### Tainting values
In the previous example, we could add many properties because we were just
making assertions about the values. When we transform the passed values, we must
be more careful about what we preserve.
While using *brands*, *flavors* and *tags* is often enough, sometimes it can be
handy to mark all the values coming from the "external world" as *tainted* (and
therefore "dangerous"), independently of whether we took the time to assign them
a specific nominal type or not.
As a simple example of what we are telling here, we can see that adding `1` to a
numeric variable would flip its parity, so in that case we wouldn't want to keep
that property on the return value.
`Nominal` provides the types `Tainted<T>` and `Untainted<T>`, both of them
operate recursively on `T`.
```typescript
import { Tainted, Untainted } from '@coderspirit/nominal'
type Even<N extends number = number> = WithProperty<N, 'Parity', 'Even'>
type Odd<N extends number = number> = WithProperty<N, 'Parity', 'Odd'>
interface LoginRequest {
username: string
password: string
}
// 1. 'Parity' is overwritten (when available)
// 2. 'Sign' is kept only if it's positive
// 3. We discard all other properties because they might stop being true
type PlusOneResult<N> = KeepProperties<
N extends Even
? KeepPropertyIfValueMatches<Odd<N>, 'Sign', 'Positive'>
: N extends Odd
? KeepPropertyIfValueMatches<Even<N>, 'Sign', 'Positive'>
: KeepPropertyIfValueMatches<N, 'Sign', 'Positive'>,
'Sign' | 'Parity'
>
type TaintedLoginRequest = Tainted<LoginRequest>
type UntaintedLoginRequest = Untainted<LoginRequest>
function validateLoginRequest(req: TaintedLoginRequest): UntaintedLoginRequest {
// throw Error if `req` is invalid
return req as UntaintedLoginRequest
function plusOne<N extends number>(v: N): PlusOneResult<N> {
return v + 1 as PlusOneResult<N>
}
// This function accepts LoginRequest and UntaintedLoginRequest,
// but not TaintedLoginRequest
function login(req: UntaintedLoginRequest): void {
// Do stuff
}
// When req is tainted, we cannot pass req.password to this function, as all
// req's fields are tainted as well.
function doStuffWithPassword(password: Untainted<string>): void {
// Do stuff
}
```
While this specific use case is far from being exciting and quite simplistic,
this idea can be applied to much more sensitive and convoluted scenarios.
### Generic tainting
While it's difficult to imagine how a value might be tainted in multiple ways,
it's not unheard of. This could be useful when managing sensitive information,
if we need/want to statically enforce that some data won't cross certain
boundaries.
```typescript
import { GenericTainted, GenericUntainted } from '@coderspirit/nominal'
type BlueTaintedNumber = GenericTainted<number, 'Blue'>
type RedTaintedNumber = GenericTainted<number, 'Red'>
// Double-tainted type!
type BlueRedTaintedNumber = GenericTainted<BlueTaintedNumber, 'Red'>
// We removed again the 'Blue' taint
type OnlyBlueTaintedNumber = GenericUntainted<BlueRedTaintedNumber, 'Red'>
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc