or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

codec.mdcombinators.mdcore-types.mddecoder.mdencoder.mdindex.mdinfrastructure.mdprimitives.mdrefinement.mdreporters.mdschema.mdtask-decoder.mdvalidation.md
tile.json

task-decoder.mddocs/

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

```