or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdbuiltin-types.mdcollection-types.mdindex.mdobject-array-validation.mdprimitive-types.mdtyped-arrays.md

advanced-features.mddocs/

0

# Advanced Features

1

2

Optional predicates, custom validation, error handling, TypeScript type utilities, and advanced validation patterns for complex use cases.

3

4

## Capabilities

5

6

### Optional Predicates

7

8

Make any predicate optional by allowing undefined values alongside the main type.

9

10

```typescript { .api }

11

/** Optional predicates interface */

12

interface OptionalPredicates {

13

// Primitive types

14

readonly string: StringPredicate & BasePredicate<string | undefined>;

15

readonly number: NumberPredicate & BasePredicate<number | undefined>;

16

readonly boolean: BooleanPredicate & BasePredicate<boolean | undefined>;

17

readonly bigint: BigIntPredicate & BasePredicate<bigint | undefined>;

18

readonly symbol: Predicate<symbol | undefined>;

19

20

// Special values

21

readonly undefined: Predicate<undefined>; // Always passes

22

readonly null: Predicate<null | undefined>;

23

readonly nullOrUndefined: Predicate<null | undefined>; // Always passes

24

readonly nan: Predicate<number | undefined>;

25

26

// Built-in types

27

readonly array: ArrayPredicate & BasePredicate<unknown[] | undefined>;

28

readonly object: ObjectPredicate & BasePredicate<object | undefined>;

29

readonly date: DatePredicate & BasePredicate<Date | undefined>;

30

readonly error: ErrorPredicate & BasePredicate<Error | undefined>;

31

readonly regExp: Predicate<RegExp | undefined>;

32

readonly promise: Predicate<Promise<unknown> | undefined>;

33

readonly function: Predicate<Function | undefined>;

34

readonly buffer: Predicate<Buffer | undefined>;

35

36

// Collection types

37

readonly map: MapPredicate & BasePredicate<Map<unknown, unknown> | undefined>;

38

readonly set: SetPredicate & BasePredicate<Set<unknown> | undefined>;

39

readonly weakMap: WeakMapPredicate & BasePredicate<WeakMap<object, unknown> | undefined>;

40

readonly weakSet: WeakSetPredicate & BasePredicate<WeakSet<object> | undefined>;

41

readonly iterable: Predicate<Iterable<unknown> | undefined>;

42

43

// Typed arrays

44

readonly typedArray: TypedArrayPredicate<TypedArray | undefined>;

45

readonly int8Array: TypedArrayPredicate<Int8Array | undefined>;

46

readonly uint8Array: TypedArrayPredicate<Uint8Array | undefined>;

47

readonly uint8ClampedArray: TypedArrayPredicate<Uint8ClampedArray | undefined>;

48

readonly int16Array: TypedArrayPredicate<Int16Array | undefined>;

49

readonly uint16Array: TypedArrayPredicate<Uint16Array | undefined>;

50

readonly int32Array: TypedArrayPredicate<Int32Array | undefined>;

51

readonly uint32Array: TypedArrayPredicate<Uint32Array | undefined>;

52

readonly float32Array: TypedArrayPredicate<Float32Array | undefined>;

53

readonly float64Array: TypedArrayPredicate<Float64Array | undefined>;

54

readonly arrayBuffer: ArrayBufferPredicate<ArrayBuffer | undefined>;

55

readonly sharedArrayBuffer: ArrayBufferPredicate<SharedArrayBuffer | undefined>;

56

readonly dataView: DataViewPredicate & BasePredicate<DataView | undefined>;

57

}

58

59

/** Access to optional predicates */

60

const optional: OptionalPredicates;

61

```

62

63

**Usage Examples:**

64

65

```typescript

66

import ow from 'ow';

67

68

// Optional primitive types

69

ow('hello', ow.optional.string);

70

ow(undefined, ow.optional.string);

71

ow(42, ow.optional.number.positive);

72

ow(undefined, ow.optional.number.positive);

73

74

// Optional objects and arrays

75

ow({ name: 'Alice' }, ow.optional.object.hasKeys('name'));

76

ow(undefined, ow.optional.object.hasKeys('name'));

77

ow([1, 2, 3], ow.optional.array.length(3));

78

ow(undefined, ow.optional.array.length(3));

79

80

// Function parameters with optional validation

81

function processUser(name: unknown, age?: unknown, email?: unknown) {

82

ow(name, 'name', ow.string.nonEmpty);

83

ow(age, 'age', ow.optional.number.integer.positive);

84

ow(email, 'email', ow.optional.string.matches(/^.+@.+\..+$/));

85

86

return {

87

name: name as string,

88

age: age as number | undefined,

89

email: email as string | undefined

90

};

91

}

92

93

// Usage

94

processUser('Alice', 30, 'alice@example.com');

95

processUser('Bob', undefined, undefined);

96

processUser('Charlie', 25); // age provided, email undefined

97

```

98

99

### Custom Validation

100

101

Advanced custom validation with flexible validation functions and error messages.

102

103

```typescript { .api }

104

/** Custom validation interfaces */

105

interface CustomValidator<T> {

106

validator: boolean;

107

message: string | MessageBuilder;

108

}

109

110

type MessageBuilder = (label?: string) => string;

111

type ValidationFunction<T> = (value: T) => boolean | string;

112

113

interface BasePredicate<T> {

114

/** Custom validation function returning boolean or error string */

115

is(validator: ValidationFunction<T>): BasePredicate<T>;

116

117

/** Custom validation with validator/message object */

118

validate(customValidator: CustomValidator<T>): BasePredicate<T>;

119

120

/** Override default error message */

121

message(newMessage: string | MessageBuilder<T>): BasePredicate<T>;

122

}

123

124

type MessageBuilder<T> = (value: T, label?: string) => string;

125

```

126

127

**Custom Function Examples:**

128

129

```typescript

130

import ow from 'ow';

131

132

// Simple boolean validation

133

ow(8, ow.number.is(x => x % 2 === 0));

134

135

// Custom validation with error message

136

ow(7, ow.number.is(x => x % 2 === 0 || `Expected even number, got ${x}`));

137

138

// Complex string validation

139

ow('password123', ow.string.is(s => {

140

if (s.length < 8) return 'Password must be at least 8 characters';

141

if (!/\d/.test(s)) return 'Password must contain at least one digit';

142

if (!/[a-z]/.test(s)) return 'Password must contain at least one lowercase letter';

143

return true;

144

}));

145

146

// Array validation

147

ow([1, 2, 3, 4], ow.array.is(arr =>

148

arr.every(x => typeof x === 'number') || 'All elements must be numbers'

149

));

150

```

151

152

**Custom Validator Object Examples:**

153

154

```typescript

155

import ow from 'ow';

156

157

// Validator object with static message

158

ow(15, ow.number.validate(value => ({

159

validator: value > 10,

160

message: `Expected number greater than 10, got ${value}`

161

})));

162

163

// Validator object with dynamic message

164

ow(5, 'threshold', ow.number.validate(value => ({

165

validator: value > 10,

166

message: label => `Expected ${label} to be greater than 10, got ${value}`

167

})));

168

169

// Complex object validation

170

const userValidator = (user: any) => ({

171

validator: user.age >= 18 && user.email.includes('@'),

172

message: 'User must be 18+ with valid email'

173

});

174

175

ow({ name: 'Alice', age: 25, email: 'alice@example.com' },

176

ow.object.validate(userValidator));

177

```

178

179

**Custom Message Examples:**

180

181

```typescript

182

import ow from 'ow';

183

184

// Static custom message

185

ow('hi', 'greeting', ow.string.minLength(5).message('Greeting is too short'));

186

187

// Dynamic custom message

188

ow(3, ow.number.positive.message((value, label) =>

189

`Expected positive ${label || 'number'}, got ${value}`

190

));

191

192

// Chained custom messages

193

ow('1234', ow.string

194

.minLength(5).message('Password too short')

195

.matches(/\d/).message('Password missing numbers')

196

);

197

```

198

199

### TypeScript Type Utilities

200

201

Advanced TypeScript integration with type inference and assertion utilities.

202

203

```typescript { .api }

204

/** Extract TypeScript type from predicate */

205

type Infer<P> = P extends BasePredicate<infer T> ? T : never;

206

207

/** Reusable validator function */

208

type ReusableValidator<T> = (value: unknown, label?: string) => void;

209

210

/** Type assertion version of reusable validator */

211

type AssertingValidator<T> = (value: unknown, label?: string) => asserts value is T;

212

213

/** Convert ReusableValidator to AssertingValidator */

214

function asAssertingValidator<T>(

215

validator: ReusableValidator<T>

216

): AssertingValidator<T>;

217

```

218

219

**Type Inference Examples:**

220

221

```typescript

222

import ow, { Infer } from 'ow';

223

224

// Extract types from predicates

225

const userPredicate = ow.object.exactShape({

226

id: ow.number.integer.positive,

227

name: ow.string.nonEmpty,

228

email: ow.optional.string.matches(/^.+@.+\..+$/),

229

roles: ow.array.ofType(ow.string.oneOf(['admin', 'user', 'guest']))

230

});

231

232

type User = Infer<typeof userPredicate>;

233

// Result: {

234

// id: number;

235

// name: string;

236

// email?: string | undefined;

237

// roles: string[];

238

// }

239

240

// Use inferred type

241

function processUser(userData: unknown): User {

242

ow(userData, userPredicate);

243

return userData as User; // TypeScript knows this is safe

244

}

245

246

// Complex nested type inference

247

const apiResponsePredicate = ow.object.exactShape({

248

data: ow.array.ofType(userPredicate),

249

meta: ow.object.exactShape({

250

total: ow.number.integer.positive,

251

page: ow.number.integer.positive,

252

hasMore: ow.boolean

253

})

254

});

255

256

type ApiResponse = Infer<typeof apiResponsePredicate>;

257

```

258

259

**Reusable Validator Examples:**

260

261

```typescript

262

import ow from 'ow';

263

264

// Create reusable validators

265

const validateEmail = ow.create('email', ow.string.matches(/^.+@.+\..+$/));

266

const validateUserId = ow.create(ow.number.integer.positive);

267

268

// Use reusable validators

269

validateEmail('alice@example.com');

270

validateUserId(123);

271

272

// Create validator with specific label

273

const validatePassword = ow.create('password',

274

ow.string.minLength(8).is(s => /\d/.test(s) || 'Must contain digit')

275

);

276

277

validatePassword('secret123');

278

279

// Type assertion validators

280

function createUser(email: unknown, id: unknown) {

281

validateEmail(email);

282

validateUserId(id);

283

284

// TypeScript knows email is string and id is number

285

return {

286

id: id as number,

287

email: email as string,

288

createdAt: new Date()

289

};

290

}

291

```

292

293

### Error Handling

294

295

Advanced error handling with ArgumentError and validation patterns.

296

297

```typescript { .api }

298

/** Error thrown when validation fails */

299

class ArgumentError extends Error {

300

readonly name: 'ArgumentError';

301

constructor(message: string);

302

}

303

304

/** Boolean validation that doesn't throw */

305

function isValid<T>(value: unknown, predicate: BasePredicate<T>): value is T;

306

```

307

308

**Error Handling Examples:**

309

310

```typescript

311

import ow, { ArgumentError } from 'ow';

312

313

// Basic error handling

314

try {

315

ow('invalid-email', ow.string.matches(/^.+@.+\..+$/));

316

} catch (error) {

317

if (error instanceof ArgumentError) {

318

console.log('Validation failed:', error.message);

319

}

320

}

321

322

// Non-throwing validation

323

function safeValidate(data: unknown) {

324

if (ow.isValid(data, ow.object.hasKeys('id', 'name'))) {

325

// TypeScript knows data is object with id and name properties

326

return { success: true, data };

327

}

328

329

return { success: false, error: 'Invalid data structure' };

330

}

331

332

// Validation with error context

333

function validateApiRequest(request: unknown) {

334

try {

335

ow(request, 'request', ow.object.exactShape({

336

method: ow.string.oneOf(['GET', 'POST', 'PUT', 'DELETE']),

337

url: ow.string.url,

338

headers: ow.optional.object,

339

body: ow.optional.any(ow.object, ow.string)

340

}));

341

342

return { valid: true, request };

343

} catch (error) {

344

if (error instanceof ArgumentError) {

345

return {

346

valid: false,

347

error: error.message,

348

type: 'validation_error'

349

};

350

}

351

352

throw error; // Re-throw unexpected errors

353

}

354

}

355

```

356

357

### Advanced Validation Patterns

358

359

Complex validation patterns for real-world scenarios.

360

361

```typescript

362

import ow from 'ow';

363

364

// Conditional validation

365

function validateUser(userData: unknown, isAdmin = false) {

366

const baseShape = {

367

id: ow.number.integer.positive,

368

name: ow.string.nonEmpty,

369

email: ow.string.matches(/^.+@.+\..+$/)

370

};

371

372

const adminShape = {

373

...baseShape,

374

permissions: ow.array.ofType(ow.string.nonEmpty),

375

lastLogin: ow.optional.date

376

};

377

378

ow(userData, isAdmin ? ow.object.exactShape(adminShape) : ow.object.exactShape(baseShape));

379

}

380

381

// Multi-step validation

382

function processFormData(formData: unknown) {

383

// Step 1: Validate basic structure

384

ow(formData, ow.object);

385

386

// Step 2: Validate required fields

387

ow(formData, ow.object.hasKeys('name', 'email'));

388

389

// Step 3: Validate field types and constraints

390

const data = formData as Record<string, unknown>;

391

ow(data.name, 'name', ow.string.nonEmpty.maxLength(100));

392

ow(data.email, 'email', ow.string.matches(/^.+@.+\..+$/));

393

394

// Step 4: Validate optional fields if present

395

if ('age' in data) {

396

ow(data.age, 'age', ow.number.integer.inRange(0, 120));

397

}

398

399

if ('website' in data) {

400

ow(data.website, 'website', ow.string.url);

401

}

402

403

return data;

404

}

405

406

// Polymorphic validation

407

function validateShape(shape: unknown) {

408

// First determine the shape type

409

ow(shape, ow.object.hasKeys('type'));

410

411

const typedShape = shape as { type: string };

412

413

switch (typedShape.type) {

414

case 'circle':

415

ow(shape, ow.object.exactShape({

416

type: ow.string.equals('circle'),

417

radius: ow.number.positive

418

}));

419

break;

420

421

case 'rectangle':

422

ow(shape, ow.object.exactShape({

423

type: ow.string.equals('rectangle'),

424

width: ow.number.positive,

425

height: ow.number.positive

426

}));

427

break;

428

429

default:

430

throw new Error(`Unknown shape type: ${typedShape.type}`);

431

}

432

}

433

434

// Recursive validation

435

const treeNodePredicate = ow.object.exactShape({

436

id: ow.string.nonEmpty,

437

value: ow.any(ow.string, ow.number),

438

children: ow.optional.array.ofType(ow.object.hasKeys('id', 'value'))

439

});

440

441

function validateTree(node: unknown) {

442

ow(node, treeNodePredicate);

443

444

const typedNode = node as any;

445

if (typedNode.children) {

446

typedNode.children.forEach((child: unknown, index: number) => {

447

validateTree(child); // Recursive validation

448

});

449

}

450

}

451

```