# 1.2.0

---

### Version Updates
- [Revision 1.2.19](https://github.com/sinclairzx81/typebox/pull/1634)
  - Optimize Modifiers Instantiation
- [Revision 1.2.18](https://github.com/sinclairzx81/typebox/pull/1633)
  - IsIdnHostname Rejects Non-Canonical Punycode
- [Revision 1.2.17](https://github.com/sinclairzx81/typebox/pull/1632)
  - Type Challenge Test Suite
- [Revision 1.2.16](https://github.com/sinclairzx81/typebox/pull/1628)
  - Handle Unreachable cases on DecodeUnsafe
- [Revision 1.2.15](https://github.com/sinclairzx81/typebox/pull/1627)
  - Engine Level Union Priority Sorting 
- [Revision 1.2.14](https://github.com/sinclairzx81/typebox/pull/1623)
  - Optimize Refine Inference
- [Revision 1.2.13](https://github.com/sinclairzx81/typebox/pull/1622)
  - Optimize Codec Inference for Parse, Encode and Decode.
- [Revision 1.2.12](https://github.com/sinclairzx81/typebox/pull/1621)
  - Ensure UnionPrioritySort Does Not Mutate Source Array
- [Revision 1.2.11](https://github.com/sinclairzx81/typebox/pull/1619)
  - Implement Extends Inference Logic for Record Types
- [Revision 1.2.10](https://github.com/sinclairzx81/typebox/pull/1618)
  - Optimize Cyclic Detection for TS7 Nightly
- [Revision 1.2.9](https://github.com/sinclairzx81/typebox/pull/1616)
  - Ensure With\<Type, ...\> Types Statically Evaluate
- [Revision 1.2.8](https://github.com/sinclairzx81/typebox/pull/1614)
  - URN-prefixed UUID is Invalid [Reference](https://github.com/sinclairzx81/typebox/pull/1614)
- [Revision 1.2.7](https://github.com/sinclairzx81/typebox/pull/1613)
  - [1612](https://github.com/sinclairzx81/typebox/pull/1612) Ensure Value.Default Object Properties are Resolved Once.
- [Revision 1.2.6](https://github.com/sinclairzx81/typebox/pull/1611)
  - Allow BigInt and Undefined Values in With Expressions
- [Revision 1.2.5](https://github.com/sinclairzx81/typebox/pull/1610)
  - Modules Only Output Declarations
- [Revision 1.2.4](https://github.com/sinclairzx81/typebox/pull/1609)
  - Integrate Modifier Instantiation via Deferred Actions
- [Revision 1.2.3](https://github.com/sinclairzx81/typebox/pull/1607)
  - Faster Cyclic Detection for Ref Instantiate
- [Revision 1.2.2](https://github.com/sinclairzx81/typebox/pull/1606)
  - Inline Keyword and Literal Rules for Depth +1 Parse
- [Revision 1.2.1](https://github.com/sinclairzx81/typebox/pull/1605)
  - Escape Decimal Separator in `NumberPattern`

<a name="overview"></a>

## Overview

Revision 1.2.0 extends TypeBox's scripting capabilities with two new constructs: `With`, a syntax for applying JSON Schema constraints inline with types, and `Dependent`, a new type to express conditionally refined types based on runtime values.

## Contents

- [Overview](#overview)
- [With Keyword](#with-keyword)
- [Dependent Types](#dependent-types)
  - [Dependent vs Conditional](#dependent-vs-conditional)
  - [Dependent Inference](#dependent-inference)
- [Evaluation Types](#evaluation-types)
- [Breaking](#breaking)

---

<a name="with-keyword"></a>

## With Keyword

Revision 1.2.0 introduces a dedicated `with` keyword used to assign JSON Schema options to types.

```typescript
const Email = Type.Script(`string with { format: 'email' }`)

// const Email = { type: 'string', format: 'email' }
```

The `with` keyword also can be used in programmatic contexts

```typescript
const { ClampedVector } = Type.Script(`

  type Clamp<T> = { [K in keyof T]: T[K] with { minimum: 0, maximum: 1 } }

  type Vector = { x: number, y: number }

  type ClampedVector = Clamp<Vector>
`)

// const ClampedVector = {
//   type: 'object',
//   required: ['x', 'y'],
//   properties: {
//     x: { type: number, minimum: 0, maximum: 1 },
//     y: { type: number, minimum: 0, maximum: 1 }
//   }
// }
```

The `with` keyword supersedes the `Options<Type, Json>` generic type introduced on 1.0.0 for augmenting types with annotations and constraints.

---

<a name="dependent-types"></a>

## Dependent Types

Revision 1.2.0 adds dependent typing support via `Type.Dependent`. This type represents JSON Schema `if`/`then`/`else` conditional schematics. When composed with existing types, dependent types enable values to be conditionally refined based on the runtime state of one or more of its properties.

Reference: https://json-schema.org/understanding-json-schema/reference/conditionals#ifthenelse

```typescript
// Type
const T = Type.Dependent(Type.Number(), Type.Literal(1), Type.String())

// Script
const T = Type.Script('if number then 1 else string')
```

Dependent types conditionally refine a value's type based on its runtime state. For example, the shape of an API response can depend on its `status` field:

```typescript
// TypeBox
const Response = Type.Script(`{
  status: 'success' | 'error'
} & (
  if { status: 'success' } then {
    data: unknown
  } else {
    message: string
    code: number
  }
)`)

// Json Schema
const Response = {
  allOf: [{
    type: "object",
    required: [ "status" ],
    properties: {
      status: {
        anyOf: [
          { type: "string", const: "success" },
          { type: "string", const: "error" }
        ]
      }
    }
  }, {
    if: {
      type: "object",
      required: [ "status" ],
      properties: {
        status: { type: "string", const: "success" }
      }
    },
    then: {
      type: "object",
      required: [ "data" ],
      properties: { data: {} }
    },
    else: {
      type: "object",
      required: [ "message", "code" ],
      properties: {
        message: { type: "string" },
        code: { type: "number" }
      }
    }
  }]
}
```

---

<a name="dependent-vs-conditional"></a>

### Dependent vs Conditional

Dependent types are similar to Conditional types, but differ in that the dependency is a runtime value rather than a statically known type. It is a subtle distinction with nuanced inference semantics.

- `Conditional`: Evaluation is immediate. Given a `Left` and a `Right` type, checks whether one extends the other and immediately collapses to either its `Then` or `Else` branches.

- `Dependent`: Evaluation is deferred. Rather than collapsing to a branch immediately, carry `If`, `Then`, and `Else` logic such that JSON Schema can conditionally evaluate a runtime value. Unlike `Conditional` where the `Left` type is known, `Dependent` inference is derived from implication (`If & Then`) and approximated negation via `Exclude<Else, If>`.

#### Approximation

Dependent types can be partially approximated via a Conditional generic.

```typescript
type D<T> = T extends number ? 1 : string

type D = if number then 1 else string
```

#### Type Signature and Inference Semantics

```typescript
//                         Left          Right         Then                   Else
const C = Type.Conditional(Type.String(), Type.String(), Type.Literal('hello'), Type.Never())
//    ^
//    TLiteral<"hello">   -- resolves immediately: String extends String, so Then

//                        If              Then                   Else
const D = Type.Dependent(Type.String(), Type.Literal('hello'), Type.Never())
//    ^
//    TDependent<TString, TLiteral<"hello">, TNever>   -- deferred: If is a runtime value
```

<a name="dependent-inference"></a>

### Dependent Inference

TypeScript does not support dependent typing, as it would require the type system to reason over runtime values. To approximate this, TypeBox lowers `Dependent` to the following expression:

```typescript
type Dependent<If, Then, Else> = (If & Then) | Exclude<Else, If>
//                                |            |
//                                |            |
//                                |            Negation:    Else holds when If does not
//                                |
//                                +----------  Implication: Then holds when If holds
```

A dependent type expression lowers as follows:

```typescript
type T = if number then 1 else string

// Dependent<number, 1, string>
// = (number & 1) | Exclude<string, number>   -- expand
// = (1)          | Exclude<string, number>   -- intersection
// = (1)          | (string)                  -- negation

type T = 1 | string
```

Some further examples:

```typescript
type A = Dependent<number, 1, string>   // 1 | string
type B = Dependent<number, 1, never>    // 1
type C = Dependent<number, 1, unknown>  // unknown
```

---

<a name="evaluation-types"></a>

## Evaluation Types

Revision 1.2.0 extends type evaluation to more logical types. In 1.1.0, evaluation was limited to `Union` and `Intersect`; 1.2.0 extends evaluation support for `Enum`, `TemplateLiteral`, and `Dependent`.

```typescript
// Intersect + Union
const Intersect = Type.Evaluate(Type.Script(`{ x: 1 } & ({ y: 1 } | { z: 1 })`))
// const Intersect: Type.TUnion<[
//   Type.TObject<{ x: Type.TLiteral<1>, y: TLiteral<1> }>,
//   Type.TObject<{ x: Type.TLiteral<1>, z: TLiteral<1> }>,
// ]>

// Enum (canonicalize as Union + deduplicate)
const Enum = Type.Evaluate(Type.Enum([1, 1, 2, 2]))
// const Enum: Type.TUnion<[
//   Type.TLiteral<1>,
//   Type.TLiteral<2>
// ]>

// TemplateLiteral: canonicalize as Union
const TemplateLiteral = Type.Evaluate(Type.Script('`hello${1|2}`'))
// const TemplateLiteral: Type.TUnion<[
//   Type.TLiteral<'hello1'>,
//   Type.TLiteral<'hello2'>
// ]>

// Dependent: canonicalize as (If & Then) | Exclude<Else, If>
const Dependent = Type.Evaluate(Type.Script('if number then 1 else string'))
// const Dependent: Type.TUnion<[
//   Type.TLiteral<1>,
//   Type.TString
// ]>
```

<a name="breaking"></a>

## Breaking

Revision 1.2.0 has two minor breaking changes.

### Enum With Null

The `Enum` type no longer accepts `null` values. This change ensures `Enum` is limited to values encodable via TypeScript `enum`, and makes provisions for a potential Script `enum` declaration type.

```typescript
// Revision: 1.1.0
const A = Type.Enum([1, 'hello', null])

// Revision: 1.2.0
const A = Type.Enum([1, 'hello', null]) // type-error: null is not TEnumValue
```

The non-null constraint is only enforced at a type-level. If you need to use `null`, you can override with `as never`, but will result in a runtime exception if the Enum is passed to `Type.Evaluate`

```typescript
const A = Type.Enum([1, 'hello', null as never]) // override
```

### Option Generic No Longer Supported in Script

The intrinsic `Options<T, Json>` generic has been removed from Script.

```typescript
// Revision: 1.1.0
const A = Type.Script('Options<string, { minLength: 10 }>')
// TOptions<string, { minLength: 10 }>

// Revision: 1.2.0
const A = Type.Script('Options<string, { minLength: 10 }>')
// TCall<TRef<"Options">, [TString, TObject<{ minLength: TLiteral<10> }>]>
```

There are two reasons for this change:

- The term `Options` is a commonly used type name. There is a high likelihood that users would create their own `Options` type in scripting contexts which would conflict with the built-in type. This naming conflict has occurred at least twice since 1.0 and was non-obvious each time.

- The 1.2.0 `with` syntax renders `Options<Type, Json>` redundant.

> Revision 1.2.0 removes the `Options<Type, Json>` from Script, but the `Type.Options(Type, Json)` function remains available on Type.* (but is flagged for deprecation). Users should migrate to `Type.With(Type, Json)`, or just use the standard last-argument option assignment pattern.

TypeBox may consider re-adding a `With<Type, Json>` type for Script the 1.2.0 revisions as a means to be able to share types between TypeBox Script and TypeScript environments.