@coderspirit/nominal
Advanced tools
Comparing version 2.1.0 to 3.0.0
@@ -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", |
320
README.md
@@ -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'> | ||
``` |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
322
27123
26
203