0
# Task Decoder API (Experimental)
1
2
**⚠️ EXPERIMENTAL:** This module is experimental and in high state of flux. Features may change without notice.
3
4
Asynchronous decoder system based on TaskEither for validating and decoding data with async operations and enhanced error handling.
5
6
## Capabilities
7
8
### TaskDecoder Interface
9
10
Core interface for asynchronous decoders that return TaskEither for composable async validation.
11
12
```typescript { .api }
13
/**
14
* Asynchronous decoder interface built on TaskEither
15
* @template I - Input type
16
* @template A - Output type after successful decoding
17
*/
18
interface TaskDecoder<I, A> extends Kleisli<TaskEither.URI, I, DecodeError, A> {
19
/** Decode function that returns TaskEither<DecodeError, A> */
20
readonly decode: (i: I) => TaskEither<DecodeError, A>;
21
}
22
23
/**
24
* Extract the decoded type from TaskDecoder
25
*/
26
type TypeOf<KTD> = KTD extends TaskDecoder<any, infer A> ? A : never;
27
28
/**
29
* Extract the input type from TaskDecoder
30
*/
31
type InputOf<KTD> = KTD extends TaskDecoder<infer I, any> ? I : never;
32
```
33
34
### Error Handling
35
36
TaskDecoder uses the same DecodeError system as the regular Decoder but wrapped in TaskEither.
37
38
```typescript { .api }
39
/**
40
* DecodeError type (same as Decoder module)
41
*/
42
type DecodeError = FreeSemigroup<DecodeError<string>>;
43
44
/**
45
* Create an error for async validation
46
*/
47
function error(actual: unknown, message: string): DecodeError;
48
49
/**
50
* Create a successful async validation result
51
*/
52
function success<A>(a: A): TaskEither<DecodeError, A>;
53
54
/**
55
* Create a failed async validation result
56
*/
57
function failure<A = never>(actual: unknown, message: string): TaskEither<DecodeError, A>;
58
```
59
60
### Constructors
61
62
Build TaskDecoders from various sources.
63
64
```typescript { .api }
65
/**
66
* Convert a synchronous Decoder to TaskDecoder
67
*/
68
function fromDecoder<I, A>(decoder: Decoder<I, A>): TaskDecoder<I, A>;
69
70
/**
71
* Create TaskDecoder from refinement function
72
*/
73
function fromRefinement<I, A extends I>(
74
refinement: Refinement<I, A>,
75
expected: string
76
): TaskDecoder<I, A>;
77
78
/**
79
* Create TaskDecoder from Guard
80
*/
81
function fromGuard<I, A extends I>(
82
guard: Guard<I, A>,
83
expected: string
84
): TaskDecoder<I, A>;
85
86
/**
87
* Create literal value TaskDecoder
88
*/
89
function literal<A extends readonly [L, ...ReadonlyArray<L>], L extends Literal = Literal>(
90
...values: A
91
): TaskDecoder<unknown, A[number]>;
92
```
93
94
**Usage Examples:**
95
96
```typescript
97
import * as TD from "io-ts/TaskDecoder";
98
import * as D from "io-ts/Decoder";
99
import { pipe } from "fp-ts/function";
100
import * as TE from "fp-ts/TaskEither";
101
102
// Convert sync decoder to async
103
const StringDecoder = TD.fromDecoder(D.string);
104
105
// Create async validation workflow
106
const validateAsync = (input: unknown) =>
107
pipe(
108
StringDecoder.decode(input),
109
TE.chain((str) =>
110
// Simulate async operation (e.g., database check)
111
TE.fromTask(async () => {
112
await new Promise(resolve => setTimeout(resolve, 100));
113
return str.toUpperCase();
114
})
115
)
116
);
117
118
// Usage
119
validateAsync("hello")().then(result => {
120
if (result._tag === 'Right') {
121
console.log('Result:', result.right); // "HELLO"
122
} else {
123
console.error('Error:', TD.draw(result.left));
124
}
125
});
126
```
127
128
### Primitive TaskDecoders
129
130
Async versions of basic type validators.
131
132
```typescript { .api }
133
/**
134
* String TaskDecoder
135
*/
136
const string: TaskDecoder<unknown, string>;
137
138
/**
139
* Number TaskDecoder
140
*/
141
const number: TaskDecoder<unknown, number>;
142
143
/**
144
* Boolean TaskDecoder
145
*/
146
const boolean: TaskDecoder<unknown, boolean>;
147
148
/**
149
* Array TaskDecoder (unknown elements)
150
*/
151
const UnknownArray: TaskDecoder<unknown, Array<unknown>>;
152
153
/**
154
* Record TaskDecoder (unknown values)
155
*/
156
const UnknownRecord: TaskDecoder<unknown, Record<string, unknown>>;
157
```
158
159
### Combinators
160
161
Functions for composing complex async validations.
162
163
```typescript { .api }
164
/**
165
* Map over validation errors with access to input
166
*/
167
function mapLeftWithInput<I>(
168
f: (input: I, e: DecodeError) => DecodeError
169
): <A>(decoder: TaskDecoder<I, A>) => TaskDecoder<I, A>;
170
171
/**
172
* Add custom error message
173
*/
174
function withMessage<I>(
175
message: (input: I, e: DecodeError) => string
176
): <A>(decoder: TaskDecoder<I, A>) => TaskDecoder<I, A>;
177
178
/**
179
* Add refinement constraint
180
*/
181
function refine<A, B extends A>(
182
refinement: Refinement<A, B>,
183
id: string
184
): <I>(from: TaskDecoder<I, A>) => TaskDecoder<I, B>;
185
186
/**
187
* Chain with async parser
188
*/
189
function parse<A, B>(
190
parser: (a: A) => TaskEither<DecodeError, B>
191
): <I>(from: TaskDecoder<I, A>) => TaskDecoder<I, B>;
192
193
/**
194
* Make TaskDecoder nullable
195
*/
196
function nullable<I, A>(or: TaskDecoder<I, A>): TaskDecoder<null | I, null | A>;
197
```
198
199
### Object Validation
200
201
Async object and struct validation.
202
203
```typescript { .api }
204
/**
205
* Create TaskDecoder for struct from property TaskDecoders
206
*/
207
function fromStruct<P extends Record<string, TaskDecoder<any, any>>>(
208
properties: P
209
): TaskDecoder<{ [K in keyof P]: InputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>;
210
211
/**
212
* Create TaskDecoder for struct from unknown input
213
*/
214
function struct<A>(
215
properties: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
216
): TaskDecoder<unknown, { [K in keyof A]: A[K] }>;
217
218
/**
219
* Create TaskDecoder for partial struct
220
*/
221
function fromPartial<P extends Record<string, TaskDecoder<any, any>>>(
222
properties: P
223
): TaskDecoder<Partial<{ [K in keyof P]: InputOf<P[K]> }>, Partial<{ [K in keyof P]: TypeOf<P[K]> }>>;
224
225
/**
226
* Create TaskDecoder for partial struct from unknown input
227
*/
228
function partial<A>(
229
properties: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
230
): TaskDecoder<unknown, Partial<{ [K in keyof A]: A[K] }>>;
231
```
232
233
### Collection Validation
234
235
Async array and record validation.
236
237
```typescript { .api }
238
/**
239
* Create TaskDecoder for arrays with typed inputs
240
*/
241
function fromArray<I, A>(item: TaskDecoder<I, A>): TaskDecoder<Array<I>, Array<A>>;
242
243
/**
244
* Create TaskDecoder for arrays from unknown input
245
*/
246
function array<A>(item: TaskDecoder<unknown, A>): TaskDecoder<unknown, Array<A>>;
247
248
/**
249
* Create TaskDecoder for records with typed inputs
250
*/
251
function fromRecord<I, A>(codomain: TaskDecoder<I, A>): TaskDecoder<Record<string, I>, Record<string, A>>;
252
253
/**
254
* Create TaskDecoder for records from unknown input
255
*/
256
function record<A>(codomain: TaskDecoder<unknown, A>): TaskDecoder<unknown, Record<string, A>>;
257
258
/**
259
* Create TaskDecoder for tuples with typed inputs
260
*/
261
function fromTuple<C extends ReadonlyArray<TaskDecoder<any, any>>>(
262
components: C
263
): TaskDecoder<{ [K in keyof C]: InputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>;
264
265
/**
266
* Create TaskDecoder for tuples from unknown input
267
*/
268
function tuple<A extends ReadonlyArray<unknown>>(
269
components: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
270
): TaskDecoder<unknown, A>;
271
```
272
273
### Advanced Combinators
274
275
Union, intersection, and recursive async validation.
276
277
```typescript { .api }
278
/**
279
* Create union TaskDecoder (tries each decoder in sequence)
280
*/
281
function union<MS extends readonly [TaskDecoder<any, any>, ...Array<TaskDecoder<any, any>>]>(
282
members: MS
283
): TaskDecoder<InputOf<MS[number]>, TypeOf<MS[number]>>;
284
285
/**
286
* Create intersection TaskDecoder
287
*/
288
function intersect<IB, B>(
289
right: TaskDecoder<IB, B>
290
): <IA, A>(left: TaskDecoder<IA, A>) => TaskDecoder<IA & IB, A & B>;
291
292
/**
293
* Create tagged union TaskDecoder
294
*/
295
function fromSum<T extends string>(
296
tag: T
297
): <MS extends Record<string, TaskDecoder<any, any>>>(
298
members: MS
299
) => TaskDecoder<InputOf<MS[keyof MS]>, TypeOf<MS[keyof MS]>>;
300
301
/**
302
* Create tagged union TaskDecoder from unknown input
303
*/
304
function sum<T extends string>(
305
tag: T
306
): <A>(members: { [K in keyof A]: TaskDecoder<unknown, A[K] & Record<T, K>> }) => TaskDecoder<unknown, A[keyof A]>;
307
308
/**
309
* Create recursive TaskDecoder
310
*/
311
function lazy<I, A>(id: string, f: () => TaskDecoder<I, A>): TaskDecoder<I, A>;
312
313
/**
314
* Make TaskDecoder readonly
315
*/
316
function readonly<I, A>(decoder: TaskDecoder<I, A>): TaskDecoder<I, Readonly<A>>;
317
```
318
319
### Functional Operations
320
321
Higher-order functions for TaskDecoder composition.
322
323
```typescript { .api }
324
/**
325
* Map over successful TaskDecoder result
326
*/
327
function map<A, B>(f: (a: A) => B): <I>(fa: TaskDecoder<I, A>) => TaskDecoder<I, B>;
328
329
/**
330
* Alternative TaskDecoder (fallback on failure)
331
*/
332
function alt<I, A>(that: () => TaskDecoder<I, A>): (me: TaskDecoder<I, A>) => TaskDecoder<I, A>;
333
334
/**
335
* Compose TaskDecoders
336
*/
337
function compose<A, B>(to: TaskDecoder<A, B>): <I>(from: TaskDecoder<I, A>) => TaskDecoder<I, B>;
338
339
/**
340
* Identity TaskDecoder
341
*/
342
function id<A>(): TaskDecoder<A, A>;
343
```
344
345
### Error Utilities
346
347
Functions for handling and displaying async validation errors.
348
349
```typescript { .api }
350
/**
351
* Convert DecodeError to human-readable string
352
*/
353
function draw(e: DecodeError): string;
354
355
/**
356
* Convert TaskEither result to human-readable string
357
*/
358
function stringify<A>(e: TaskEither<DecodeError, A>): Task<string>;
359
```
360
361
## Usage Examples
362
363
### Basic Async Validation
364
365
```typescript
366
import * as TD from "io-ts/TaskDecoder";
367
import { pipe } from "fp-ts/function";
368
import * as TE from "fp-ts/TaskEither";
369
370
const User = TD.struct({
371
id: TD.number,
372
name: TD.string,
373
email: TD.string
374
});
375
376
// Async validation with custom logic
377
const validateUser = (data: unknown) =>
378
pipe(
379
User.decode(data),
380
TE.chain(user =>
381
// Add async email validation
382
user.email.includes('@')
383
? TD.success(user)
384
: TD.failure(user.email, 'Invalid email format')
385
)
386
);
387
388
// Usage
389
validateUser({ id: 1, name: "Alice", email: "alice@example.com" })()
390
.then(result => {
391
if (result._tag === 'Right') {
392
console.log('Valid user:', result.right);
393
} else {
394
console.error('Validation error:', TD.draw(result.left));
395
}
396
});
397
```
398
399
### Database Validation
400
401
```typescript
402
import * as TD from "io-ts/TaskDecoder";
403
import { pipe } from "fp-ts/function";
404
import * as TE from "fp-ts/TaskEither";
405
406
// Simulate database check
407
const checkUserExists = (id: number): TE.TaskEither<string, boolean> =>
408
TE.fromTask(async () => {
409
// Simulate API call
410
await new Promise(resolve => setTimeout(resolve, 100));
411
return id > 0; // Simple validation logic
412
});
413
414
const ExistingUser = pipe(
415
TD.number,
416
TD.parse(id =>
417
pipe(
418
checkUserExists(id),
419
TE.mapLeft(error => TD.error(id, `User validation failed: ${error}`)),
420
TE.chain(exists =>
421
exists
422
? TE.right(id)
423
: TE.left(TD.error(id, 'User does not exist'))
424
)
425
)
426
)
427
);
428
429
// Usage
430
ExistingUser.decode(123)().then(result => {
431
if (result._tag === 'Right') {
432
console.log('User ID valid:', result.right);
433
} else {
434
console.error('User validation failed:', TD.draw(result.left));
435
}
436
});
437
```
438
439
### Complex Async Workflow
440
441
```typescript
442
import * as TD from "io-ts/TaskDecoder";
443
import { pipe } from "fp-ts/function";
444
import * as TE from "fp-ts/TaskEither";
445
import * as T from "fp-ts/Task";
446
447
// Multi-step async validation
448
const ComplexValidation = TD.struct({
449
username: pipe(
450
TD.string,
451
TD.refine(s => s.length >= 3, 'Username too short'),
452
TD.parse(username =>
453
// Check username availability
454
TE.fromTask(async () => {
455
await new Promise(resolve => setTimeout(resolve, 50));
456
if (username === 'admin') {
457
throw TD.error(username, 'Username not available');
458
}
459
return username;
460
})
461
)
462
),
463
email: pipe(
464
TD.string,
465
TD.refine(s => s.includes('@'), 'Invalid email'),
466
TD.parse(email =>
467
// Validate email format async
468
TE.fromTask(async () => {
469
await new Promise(resolve => setTimeout(resolve, 50));
470
return email.toLowerCase();
471
})
472
)
473
)
474
});
475
476
// Usage with error handling
477
const validateComplexData = (data: unknown) =>
478
pipe(
479
ComplexValidation.decode(data),
480
TE.fold(
481
error => T.of(`Validation failed: ${TD.draw(error)}`),
482
success => T.of(`Validation succeeded: ${JSON.stringify(success)}`)
483
)
484
);
485
486
validateComplexData({ username: "alice", email: "ALICE@EXAMPLE.COM" })()
487
.then(console.log);
488
```
489
490
## Type Class Instances
491
492
TaskDecoder implements several functional programming type classes:
493
494
```typescript { .api }
495
/**
496
* Functor instance for mapping over successful results
497
*/
498
const Functor: Functor2<TaskDecoder.URI>;
499
500
/**
501
* Alt instance for providing alternative decoders
502
*/
503
const Alt: Alt2<TaskDecoder.URI>;
504
505
/**
506
* Category instance for composition
507
*/
508
const Category: Category2<TaskDecoder.URI>;
509
510
/**
511
* Schemable instance for building schemas
512
*/
513
const Schemable: Schemable2C<TaskDecoder.URI, unknown>;
514
515
/**
516
* WithUnknownContainers instance
517
*/
518
const WithUnknownContainers: WithUnknownContainers2C<TaskDecoder.URI, unknown>;
519
520
/**
521
* WithUnion instance for union types
522
*/
523
const WithUnion: WithUnion2C<TaskDecoder.URI, unknown>;
524
525
/**
526
* WithRefine instance for refinement types
527
*/
528
const WithRefine: WithRefine2C<TaskDecoder.URI, unknown>;
529
```
530
531
## Deprecated Functions
532
533
```typescript { .api }
534
/**
535
* @deprecated Use fromStruct instead
536
*/
537
const fromType: typeof fromStruct;
538
539
/**
540
* @deprecated Use struct instead
541
*/
542
const type: typeof struct;
543
```