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
```