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

utilities.mddocs/

0

# Utilities

1

2

Pattern matching and helper utilities for working with runtypes. These tools provide additional functionality for advanced validation scenarios and convenient type manipulation.

3

4

## Capabilities

5

6

### Pattern Matching

7

8

Pattern matching utilities for working with union types and discriminated unions.

9

10

```typescript { .api }

11

/**

12

* Creates a pattern matcher for union types

13

* @param cases - Array of case handlers created with when()

14

* @returns Function that matches value against cases

15

* @example match(when(String, s => s.length), when(Number, n => n * 2))

16

*/

17

function match<C extends readonly Case[]>(...cases: C): Matcher<C>;

18

19

/**

20

* Creates a case for pattern matching

21

* @param runtype - Runtype to match against

22

* @param transformer - Function to execute when matched

23

* @returns Case object for use with match()

24

* @example when(String, s => s.toUpperCase())

25

*/

26

function when<T, U>(runtype: Runtype<T>, transformer: (value: T) => U): Case<T, U>;

27

28

type Matcher<C> = (value: unknown) => ReturnType<C[number]>;

29

type Case<T, U> = [runtype: Runtype<T>, transformer: (value: T) => U];

30

```

31

32

**Usage Examples:**

33

34

```typescript

35

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

36

37

// Basic pattern matching

38

const processValue = match(

39

when(String, str => `String: ${str.toUpperCase()}`),

40

when(Number, num => `Number: ${num * 2}`),

41

when(Boolean, bool => `Boolean: ${bool ? "yes" : "no"}`)

42

);

43

44

console.log(processValue("hello")); // "String: HELLO"

45

console.log(processValue(42)); // "Number: 84"

46

console.log(processValue(true)); // "Boolean: yes"

47

48

// Discriminated union pattern matching

49

const Shape = Union(

50

Object({ type: Literal("circle"), radius: Number }),

51

Object({ type: Literal("rectangle"), width: Number, height: Number }),

52

Object({ type: Literal("triangle"), base: Number, height: Number })

53

);

54

55

const calculateArea = match(

56

when(Object({ type: Literal("circle"), radius: Number }),

57

({ radius }) => Math.PI * radius ** 2),

58

when(Object({ type: Literal("rectangle"), width: Number, height: Number }),

59

({ width, height }) => width * height),

60

when(Object({ type: Literal("triangle"), base: Number, height: Number }),

61

({ base, height }) => (base * height) / 2)

62

);

63

64

const area = calculateArea({ type: "circle", radius: 5 }); // ~78.54

65

```

66

67

### API Response Pattern Matching

68

69

```typescript

70

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

71

72

// API response types

73

const SuccessResponse = Object({

74

status: Literal("success"),

75

data: Unknown

76

});

77

78

const ErrorResponse = Object({

79

status: Literal("error"),

80

message: String,

81

code: Number

82

});

83

84

const LoadingResponse = Object({

85

status: Literal("loading"),

86

progress: Number.optional()

87

});

88

89

// Pattern matcher for responses

90

const handleApiResponse = match(

91

when(SuccessResponse, ({ data }) => ({

92

type: "success" as const,

93

payload: data

94

})),

95

when(ErrorResponse, ({ message, code }) => ({

96

type: "error" as const,

97

error: `${code}: ${message}`

98

})),

99

when(LoadingResponse, ({ progress = 0 }) => ({

100

type: "loading" as const,

101

progress

102

}))

103

);

104

105

// Usage

106

const response1 = handleApiResponse({ status: "success", data: { users: [] } });

107

// { type: "success", payload: { users: [] } }

108

109

const response2 = handleApiResponse({ status: "error", message: "Not found", code: 404 });

110

// { type: "error", error: "404: Not found" }

111

```

112

113

### Advanced Pattern Matching

114

115

```typescript

116

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

117

118

// Complex data processing

119

const DataProcessor = match(

120

// Process strings

121

when(String, str => ({

122

type: "text",

123

length: str.length,

124

words: str.split(" ").length,

125

uppercase: str.toUpperCase()

126

})),

127

128

// Process numbers

129

when(Number, num => ({

130

type: "numeric",

131

value: num,

132

squared: num ** 2,

133

isEven: num % 2 === 0

134

})),

135

136

// Process arrays

137

when(Array(Unknown), arr => ({

138

type: "list",

139

count: arr.length,

140

isEmpty: arr.length === 0,

141

first: arr[0],

142

last: arr[arr.length - 1]

143

})),

144

145

// Process objects

146

when(Object({ name: String, age: Number }), person => ({

147

type: "person",

148

greeting: `Hello, ${person.name}!`,

149

isAdult: person.age >= 18,

150

category: person.age < 13 ? "child" : person.age < 20 ? "teen" : "adult"

151

}))

152

);

153

154

// Usage examples

155

console.log(DataProcessor("Hello World"));

156

// { type: "text", length: 11, words: 2, uppercase: "HELLO WORLD" }

157

158

console.log(DataProcessor(42));

159

// { type: "numeric", value: 42, squared: 1764, isEven: true }

160

161

console.log(DataProcessor([1, 2, 3]));

162

// { type: "list", count: 3, isEmpty: false, first: 1, last: 3 }

163

164

console.log(DataProcessor({ name: "Alice", age: 25 }));

165

// { type: "person", greeting: "Hello, Alice!", isAdult: true, category: "adult" }

166

```

167

168

## Built-in Union Pattern Matching

169

170

Union types also provide their own pattern matching method.

171

172

```typescript

173

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

174

175

const Result = Union(

176

Object({ type: Literal("ok"), value: String }),

177

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

178

);

179

180

// Using Union's built-in match method

181

const processResult = Result.match(

182

// Case for "ok" variant

183

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

184

// Case for "error" variant

185

({ message }) => `Failed: ${message}`

186

);

187

188

const result1 = processResult({ type: "ok", value: "Hello" }); // "Success: Hello"

189

const result2 = processResult({ type: "error", message: "Oops" }); // "Failed: Oops"

190

```

191

192

## Additional Utilities

193

194

### Optional Helper

195

196

While Optional is primarily used in object definitions, it can be useful for creating optional validation chains.

197

198

```typescript

199

import { Optional, String, Number } from "runtypes";

200

201

// Create optional validators

202

const OptionalString = Optional(String);

203

const OptionalNumber = Optional(Number, 0); // with default

204

205

// Check for optional presence

206

function processOptionalValue(value: unknown) {

207

if (Optional.isOptional(OptionalString)) {

208

console.log("This is an optional type");

209

}

210

211

// Optional validation allows undefined

212

const result = OptionalString.underlying.guard(value);

213

return result;

214

}

215

```

216

217

### Lazy Evaluation

218

219

For recursive type definitions and forward references.

220

221

```typescript

222

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

223

224

// Recursive data structures

225

type TreeNodeType = {

226

value: string;

227

children: TreeNodeType[];

228

};

229

230

const TreeNode: Runtype<TreeNodeType> = Lazy(() => Object({

231

value: String,

232

children: Array(TreeNode) // Self-reference

233

}));

234

235

// Usage

236

const tree = TreeNode.check({

237

value: "root",

238

children: [

239

{

240

value: "child1",

241

children: [

242

{ value: "grandchild1", children: [] },

243

{ value: "grandchild2", children: [] }

244

]

245

},

246

{ value: "child2", children: [] }

247

]

248

});

249

250

// Mutually recursive types

251

type PersonType = {

252

name: string;

253

company?: CompanyType;

254

};

255

256

type CompanyType = {

257

name: string;

258

employees: PersonType[];

259

};

260

261

const Person: Runtype<PersonType> = Lazy(() => Object({

262

name: String,

263

company: Company.optional()

264

}));

265

266

const Company: Runtype<CompanyType> = Lazy(() => Object({

267

name: String,

268

employees: Array(Person)

269

}));

270

```

271

272

### InstanceOf Utility

273

274

Validate instanceof relationships with constructor functions.

275

276

```typescript

277

import { InstanceOf, Object, String, Union } from "runtypes";

278

279

// Built-in constructors

280

const DateValidator = InstanceOf(Date);

281

const RegExpValidator = InstanceOf(RegExp);

282

const ErrorValidator = InstanceOf(Error);

283

284

const date = DateValidator.check(new Date()); // Date

285

const regex = RegExpValidator.check(/pattern/); // RegExp

286

287

// Custom classes

288

class User {

289

constructor(public name: string, public age: number) {}

290

}

291

292

class Admin extends User {

293

constructor(name: string, age: number, public permissions: string[]) {

294

super(name, age);

295

}

296

}

297

298

const UserValidator = InstanceOf(User);

299

const AdminValidator = InstanceOf(Admin);

300

301

const user = new User("Alice", 25);

302

const admin = new Admin("Bob", 30, ["read", "write"]);

303

304

UserValidator.check(user); // ✓ User instance

305

UserValidator.check(admin); // ✓ Admin extends User

306

AdminValidator.check(admin); // ✓ Admin instance

307

AdminValidator.check(user); // ✗ throws ValidationError

308

309

// Combined with other validators

310

const UserData = Union(

311

InstanceOf(User),

312

Object({ name: String, age: Number }) // Plain object alternative

313

);

314

```

315

316

### Function Validation

317

318

Basic function validation (note: cannot validate function signatures at runtime).

319

320

```typescript

321

import { Function as FunctionValidator } from "runtypes";

322

323

// Validate that something is a function

324

const fn = FunctionValidator.check(() => "hello"); // Function

325

const method = FunctionValidator.check(Math.max); // Function

326

327

// Combined with other types

328

const Callback = Union(FunctionValidator, Literal(null));

329

330

function processWithCallback(data: unknown, callback: unknown) {

331

const validCallback = Callback.check(callback);

332

333

if (typeof validCallback === "function") {

334

return validCallback(data);

335

}

336

337

return data; // No callback provided

338

}

339

```

340

341

### Spread Utility

342

343

Enable rest/spread syntax in tuple definitions.

344

345

```typescript

346

import { Spread, Tuple, Array, String, Number } from "runtypes";

347

348

// Tuple with rest elements

349

const LogEntry = Tuple(

350

String, // timestamp

351

String, // level

352

Spread(Array(String)) // variable message parts

353

);

354

355

const entry1 = LogEntry.check(["2024-01-15T10:30:00Z", "INFO", "User", "logged", "in"]);

356

const entry2 = LogEntry.check(["2024-01-15T10:31:00Z", "ERROR", "Database", "connection", "failed", "retry", "in", "5s"]);

357

358

// Mixed tuple with leading, rest, and trailing

359

const DataRecord = Tuple(

360

Number, // id

361

String, // name

362

Spread(Array(Number)), // scores (variable length)

363

Boolean // active

364

);

365

366

const record = DataRecord.check([1, "Alice", 95, 87, 92, 88, true]);

367

// id=1, name="Alice", scores=[95,87,92,88], active=true

368

```

369

370

## Utility Composition

371

372

```typescript

373

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

374

375

// Complex validation and processing pipeline

376

const DataInput = Union(

377

String,

378

Number,

379

Array(String),

380

Object({ type: Literal("user"), name: String, age: Number }),

381

Object({ type: Literal("product"), name: String, price: Number })

382

);

383

384

const processInput = match(

385

when(String, str => ({ kind: "text", processed: str.trim().toLowerCase() })),

386

when(Number, num => ({ kind: "numeric", processed: num.toString() })),

387

when(Array(String), arr => ({ kind: "list", processed: arr.join(", ") })),

388

when(Object({ type: Literal("user"), name: String, age: Number }),

389

user => ({ kind: "user", processed: `${user.name} (${user.age})` })),

390

when(Object({ type: Literal("product"), name: String, price: Number }),

391

product => ({ kind: "product", processed: `${product.name}: $${product.price}` }))

392

);

393

394

// Usage

395

const results = [

396

processInput(" Hello World "),

397

processInput(42),

398

processInput(["apple", "banana", "cherry"]),

399

processInput({ type: "user", name: "Alice", age: 25 }),

400

processInput({ type: "product", name: "Widget", price: 19.99 })

401

];

402

403

results.forEach(result => {

404

console.log(`${result.kind}: ${result.processed}`);

405

});

406

// text: hello world

407

// numeric: 42

408

// list: apple, banana, cherry

409

// user: Alice (25)

410

// product: Widget: $19.99

411

```