or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

composite.mdconstraints.mdcontracts.mdindex.mdliterals.mdprimitives.mdresults.mdtemplates.mdunion-intersect.mdutilities.mdvalidation.md

union-intersect.mddocs/

0

# Union and Intersection Types

1

2

Combines multiple runtypes to create flexible validation schemas. Union types validate values that match any of several alternatives, while intersection types require values to match all specified types.

3

4

## Capabilities

5

6

### Union

7

8

Validates values that match at least one of the provided alternative runtypes.

9

10

```typescript { .api }

11

/**

12

* Creates a validator that accepts values matching any of the provided alternatives

13

* @param alternatives - Array of runtypes, value must match at least one

14

* @example Union(String, Number).check("hello") // "hello"

15

* @example Union(String, Number).check(42) // 42

16

* @example Union(String, Number).check(true) // throws ValidationError

17

*/

18

function Union<T extends readonly Runtype[]>(...alternatives: T): UnionRuntype<T>;

19

20

interface UnionRuntype<T> extends Runtype<UnionType<T>> {

21

tag: "union";

22

alternatives: T;

23

match<C extends Cases<T>>(...cases: C): Matcher<T, ReturnType<C[number]>>;

24

}

25

```

26

27

**Usage Examples:**

28

29

```typescript

30

import { Union, String, Number, Boolean, Literal, Object } from "runtypes";

31

32

// Basic union types

33

const StringOrNumber = Union(String, Number);

34

const value1 = StringOrNumber.check("hello"); // "hello"

35

const value2 = StringOrNumber.check(42); // 42

36

37

// Multiple alternatives

38

const ID = Union(String, Number, BigInt);

39

const userId = ID.check("user_123"); // "user_123"

40

const postId = ID.check(456); // 456

41

42

// Literal unions (enum-like)

43

const Status = Union(

44

Literal("pending"),

45

Literal("approved"),

46

Literal("rejected"),

47

Literal("cancelled")

48

);

49

50

type StatusType = Static<typeof Status>; // "pending" | "approved" | "rejected" | "cancelled"

51

52

// Nullable types using union

53

const OptionalString = Union(String, Literal(null));

54

const OptionalNumber = Union(Number, Literal(undefined));

55

56

// Or use built-in helpers

57

const NullableString = String.nullable(); // Union(String, Literal(null))

58

const UndefinedableNumber = Number.undefinedable(); // Union(Number, Literal(undefined))

59

```

60

61

### Discriminated Unions

62

63

Create type-safe discriminated unions for complex data structures.

64

65

```typescript

66

import { Union, Object, Literal, String, Number, Array } from "runtypes";

67

68

// Shape discriminated union

69

const Circle = Object({

70

type: Literal("circle"),

71

radius: Number,

72

center: Object({ x: Number, y: Number })

73

});

74

75

const Rectangle = Object({

76

type: Literal("rectangle"),

77

width: Number,

78

height: Number,

79

topLeft: Object({ x: Number, y: Number })

80

});

81

82

const Triangle = Object({

83

type: Literal("triangle"),

84

vertices: Array(Object({ x: Number, y: Number }))

85

});

86

87

const Shape = Union(Circle, Rectangle, Triangle);

88

89

type ShapeType = Static<typeof Shape>;

90

// { type: "circle", radius: number, center: { x: number, y: number } } |

91

// { type: "rectangle", width: number, height: number, topLeft: { x: number, y: number } } |

92

// { type: "triangle", vertices: { x: number, y: number }[] }

93

94

// Usage with type narrowing

95

function calculateArea(shape: unknown): number {

96

const validShape = Shape.check(shape);

97

98

switch (validShape.type) {

99

case "circle":

100

return Math.PI * validShape.radius ** 2;

101

case "rectangle":

102

return validShape.width * validShape.height;

103

case "triangle":

104

// Complex triangle area calculation

105

return 0; // simplified

106

}

107

}

108

```

109

110

### Pattern Matching with Union

111

112

Use the built-in pattern matching functionality for unions.

113

114

```typescript

115

import { Union, match, when, Literal, Object, String, Number } from "runtypes";

116

117

const Result = Union(

118

Object({ type: Literal("success"), data: String }),

119

Object({ type: Literal("error"), message: String, code: Number }),

120

Object({ type: Literal("loading") })

121

);

122

123

// Pattern matching

124

const handleResult = match(

125

when(Object({ type: Literal("success"), data: String }),

126

({ data }) => `Success: ${data}`),

127

when(Object({ type: Literal("error"), message: String, code: Number }),

128

({ message, code }) => `Error ${code}: ${message}`),

129

when(Object({ type: Literal("loading") }),

130

() => "Loading...")

131

);

132

133

// Usage

134

const response = handleResult({ type: "success", data: "Hello World" });

135

// "Success: Hello World"

136

```

137

138

### Intersect

139

140

Validates values that match all of the provided runtypes simultaneously.

141

142

```typescript { .api }

143

/**

144

* Creates a validator that requires values to match all provided runtypes

145

* @param intersectees - Array of runtypes, value must match all of them

146

* @example Intersect(Object({name: String}), Object({age: Number})).check({name: "Alice", age: 25})

147

*/

148

function Intersect<T extends readonly Runtype[]>(...intersectees: T): IntersectRuntype<T>;

149

150

interface IntersectRuntype<T> extends Runtype<IntersectType<T>> {

151

tag: "intersect";

152

intersectees: T;

153

}

154

```

155

156

**Usage Examples:**

157

158

```typescript

159

import { Intersect, Object, String, Number, Boolean } from "runtypes";

160

161

// Combining object types

162

const PersonBase = Object({

163

name: String,

164

age: Number

165

});

166

167

const ContactInfo = Object({

168

email: String,

169

phone: String.optional()

170

});

171

172

const PersonWithContact = Intersect(PersonBase, ContactInfo);

173

174

type PersonWithContactType = Static<typeof PersonWithContact>;

175

// {

176

// name: string;

177

// age: number;

178

// email: string;

179

// phone?: string;

180

// }

181

182

const person = PersonWithContact.check({

183

name: "Alice",

184

age: 25,

185

email: "alice@example.com",

186

phone: "555-0123"

187

});

188

```

189

190

### Mixin Patterns with Intersect

191

192

```typescript

193

import { Intersect, Object, String, Number, Boolean } from "runtypes";

194

195

// Base entity

196

const BaseEntity = Object({

197

id: String,

198

createdAt: Number,

199

updatedAt: Number

200

});

201

202

// Audit trail

203

const Auditable = Object({

204

createdBy: String,

205

updatedBy: String,

206

version: Number

207

});

208

209

// Soft delete capability

210

const SoftDeletable = Object({

211

deleted: Boolean,

212

deletedAt: Number.optional(),

213

deletedBy: String.optional()

214

});

215

216

// User entity with mixins

217

const User = Intersect(

218

BaseEntity,

219

Auditable,

220

SoftDeletable,

221

Object({

222

name: String,

223

email: String,

224

role: Union(Literal("admin"), Literal("user"))

225

})

226

);

227

228

type UserType = Static<typeof User>;

229

// Combines all properties from all intersected types

230

```

231

232

## Advanced Union Patterns

233

234

### Async Result Types

235

236

```typescript

237

import { Union, Object, Literal, String, Unknown } from "runtypes";

238

239

const AsyncResult = Union(

240

Object({

241

status: Literal("pending")

242

}),

243

Object({

244

status: Literal("fulfilled"),

245

value: Unknown

246

}),

247

Object({

248

status: Literal("rejected"),

249

reason: String

250

})

251

);

252

253

// Usage in async contexts

254

async function handleAsyncResult(result: unknown) {

255

const validResult = AsyncResult.check(result);

256

257

if (validResult.status === "fulfilled") {

258

console.log("Success:", validResult.value);

259

} else if (validResult.status === "rejected") {

260

console.error("Error:", validResult.reason);

261

} else {

262

console.log("Still pending...");

263

}

264

}

265

```

266

267

### Conditional Types with Union

268

269

```typescript

270

import { Union, Object, Literal, String, Number, Array } from "runtypes";

271

272

// API endpoint responses

273

const UserResponse = Object({

274

type: Literal("user"),

275

data: Object({

276

id: Number,

277

name: String,

278

email: String

279

})

280

});

281

282

const PostResponse = Object({

283

type: Literal("post"),

284

data: Object({

285

id: Number,

286

title: String,

287

content: String,

288

authorId: Number

289

})

290

});

291

292

const ListResponse = Object({

293

type: Literal("list"),

294

data: Array(Unknown),

295

pagination: Object({

296

page: Number,

297

total: Number,

298

hasMore: Boolean

299

})

300

});

301

302

const ApiResponse = Union(UserResponse, PostResponse, ListResponse);

303

304

// Type-safe response handling

305

function processResponse(response: unknown) {

306

const validResponse = ApiResponse.check(response);

307

308

switch (validResponse.type) {

309

case "user":

310

// TypeScript knows data is user object

311

return `User: ${validResponse.data.name}`;

312

case "post":

313

// TypeScript knows data is post object

314

return `Post: ${validResponse.data.title}`;

315

case "list":

316

// TypeScript knows about pagination

317

return `List: ${validResponse.data.length} items (page ${validResponse.pagination.page})`;

318

}

319

}

320

```

321

322

## Combining Union and Intersection

323

324

```typescript

325

import { Union, Intersect, Object, Literal, String, Number } from "runtypes";

326

327

// Base types

328

const Identifiable = Object({ id: String });

329

const Timestamped = Object({ timestamp: Number });

330

331

// Different entity types

332

const User = Object({ type: Literal("user"), name: String, email: String });

333

const Post = Object({ type: Literal("post"), title: String, content: String });

334

335

// Combine with mixins using intersection, then union for alternatives

336

const TimestampedUser = Intersect(User, Identifiable, Timestamped);

337

const TimestampedPost = Intersect(Post, Identifiable, Timestamped);

338

339

const Entity = Union(TimestampedUser, TimestampedPost);

340

341

type EntityType = Static<typeof Entity>;

342

// ({ type: "user", name: string, email: string } & { id: string } & { timestamp: number }) |

343

// ({ type: "post", title: string, content: string } & { id: string } & { timestamp: number })

344

```