or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

branded-types-validation.mdcollection-codecs.mddate-handling.mdfunctional-programming-types.mdindex.mdjson-handling.mdstring-number-transformations.mdutility-functions-codec-modifiers.md
tile.json

functional-programming-types.mddocs/

0

# Functional Programming Types

1

2

Codecs for fp-ts Option and Either types with JSON serialization support, enabling functional programming patterns in API communication. These codecs allow safe serialization and deserialization of functional programming constructs to and from JSON representations.

3

4

## Capabilities

5

6

### Option Type Codec

7

8

Creates codecs for fp-ts Option types with JSON serialization, allowing None and Some values to be transmitted over networks and stored in databases.

9

10

```typescript { .api }

11

/**

12

* Output type for None values in Option serialization

13

*/

14

type NoneOutput = { _tag: 'None' };

15

16

/**

17

* Output type for Some values in Option serialization

18

*/

19

type SomeOutput<A> = { _tag: 'Some'; value: A };

20

21

/**

22

* Union type for Option serialization output

23

*/

24

type OptionOutput<A> = NoneOutput | SomeOutput<A>;

25

26

/**

27

* Type interface for Option codec

28

*/

29

interface OptionC<C extends t.Mixed>

30

extends t.Type<Option<t.TypeOf<C>>, OptionOutput<t.OutputOf<C>>, unknown> {}

31

32

/**

33

* Creates a codec for Option<A> that can serialize/deserialize JSON representations

34

* @param codec - The inner codec for the Some value type

35

* @param name - Optional name for the codec

36

* @returns Option codec with JSON serialization support

37

*/

38

function option<C extends t.Mixed>(codec: C, name?: string): OptionC<C>;

39

```

40

41

**Usage Examples:**

42

43

```typescript

44

import { option } from "io-ts-types";

45

import * as t from "io-ts";

46

import * as O from "fp-ts/lib/Option";

47

48

// Create codec for optional string

49

const OptionalString = option(t.string);

50

51

// Decode Some value

52

const someResult = OptionalString.decode({ _tag: "Some", value: "hello" });

53

// Right(some("hello"))

54

55

// Decode None value

56

const noneResult = OptionalString.decode({ _tag: "None" });

57

// Right(none)

58

59

// Invalid structure

60

const invalidResult = OptionalString.decode({ _tag: "Invalid" });

61

// Left([ValidationError])

62

63

// Encoding Some to JSON

64

const someValue = O.some("world");

65

const encodedSome = OptionalString.encode(someValue);

66

// { _tag: "Some", value: "world" }

67

68

// Encoding None to JSON

69

const noneValue = O.none;

70

const encodedNone = OptionalString.encode(noneValue);

71

// { _tag: "None" }

72

```

73

74

### Option From Nullable

75

76

Creates Option codecs that treat null/undefined as None and other values as Some, providing a bridge between nullable values and Option types.

77

78

```typescript { .api }

79

/**

80

* Type interface for OptionFromNullable codec

81

*/

82

interface OptionFromNullableC<C extends t.Mixed>

83

extends t.Type<Option<t.TypeOf<C>>, t.OutputOf<C> | null, unknown> {}

84

85

/**

86

* Creates a codec for Option<A> that treats null/undefined as None

87

* @param codec - The inner codec for the Some value type

88

* @param name - Optional name for the codec

89

* @returns Option codec that handles nullable inputs

90

*/

91

function optionFromNullable<C extends t.Mixed>(

92

codec: C,

93

name?: string

94

): OptionFromNullableC<C>;

95

```

96

97

**Usage Examples:**

98

99

```typescript

100

import { optionFromNullable } from "io-ts-types";

101

import * as t from "io-ts";

102

import * as O from "fp-ts/lib/Option";

103

104

const OptionalStringFromNull = optionFromNullable(t.string);

105

106

// Decode regular value as Some

107

const result1 = OptionalStringFromNull.decode("hello");

108

// Right(some("hello"))

109

110

// Decode null as None

111

const result2 = OptionalStringFromNull.decode(null);

112

// Right(none)

113

114

// Decode undefined as None

115

const result3 = OptionalStringFromNull.decode(undefined);

116

// Right(none)

117

118

// Invalid inner type

119

const result4 = OptionalStringFromNull.decode(123);

120

// Left([ValidationError]) - number is not a string

121

122

// Encoding Some back to regular value

123

const someValue = O.some("world");

124

const encoded1 = OptionalStringFromNull.encode(someValue);

125

// "world"

126

127

// Encoding None back to null

128

const noneValue = O.none;

129

const encoded2 = OptionalStringFromNull.encode(noneValue);

130

// null

131

```

132

133

### Either Type Codec

134

135

Creates codecs for fp-ts Either types with JSON serialization, allowing Left and Right values to be transmitted and stored.

136

137

```typescript { .api }

138

/**

139

* Output type for Left values in Either serialization

140

*/

141

type LeftOutput<L> = { _tag: 'Left'; left: L };

142

143

/**

144

* Output type for Right values in Either serialization

145

*/

146

type RightOutput<R> = { _tag: 'Right'; right: R };

147

148

/**

149

* Union type for Either serialization output

150

*/

151

type EitherOutput<L, R> = LeftOutput<L> | RightOutput<R>;

152

153

/**

154

* Type interface for Either codec

155

*/

156

interface EitherC<L extends t.Mixed, R extends t.Mixed>

157

extends t.Type<Either<t.TypeOf<L>, t.TypeOf<R>>, EitherOutput<t.OutputOf<L>, t.OutputOf<R>>, unknown> {}

158

159

/**

160

* Creates a codec for Either<L, R> that can serialize/deserialize JSON representations

161

* @param leftCodec - Codec for the Left value type

162

* @param rightCodec - Codec for the Right value type

163

* @param name - Optional name for the codec

164

* @returns Either codec with JSON serialization support

165

*/

166

function either<L extends t.Mixed, R extends t.Mixed>(

167

leftCodec: L,

168

rightCodec: R,

169

name?: string

170

): EitherC<L, R>;

171

```

172

173

**Usage Examples:**

174

175

```typescript

176

import { either } from "io-ts-types";

177

import * as t from "io-ts";

178

import * as E from "fp-ts/lib/Either";

179

180

// Create codec for Either<string, number>

181

const StringOrNumber = either(t.string, t.number);

182

183

// Decode Right value

184

const rightResult = StringOrNumber.decode({ _tag: "Right", right: 42 });

185

// Right(right(42))

186

187

// Decode Left value

188

const leftResult = StringOrNumber.decode({ _tag: "Left", left: "error" });

189

// Right(left("error"))

190

191

// Invalid structure

192

const invalidResult = StringOrNumber.decode({ _tag: "Invalid" });

193

// Left([ValidationError])

194

195

// Invalid Right type

196

const invalidRight = StringOrNumber.decode({ _tag: "Right", right: true });

197

// Left([ValidationError]) - boolean is not a number

198

199

// Encoding Right to JSON

200

const rightValue = E.right(100);

201

const encodedRight = StringOrNumber.encode(rightValue);

202

// { _tag: "Right", right: 100 }

203

204

// Encoding Left to JSON

205

const leftValue = E.left("failure");

206

const encodedLeft = StringOrNumber.encode(leftValue);

207

// { _tag: "Left", left: "failure" }

208

```

209

210

## Common Usage Patterns

211

212

### API Error Handling

213

214

```typescript

215

import * as t from "io-ts";

216

import { either, option } from "io-ts-types";

217

import * as E from "fp-ts/lib/Either";

218

import * as O from "fp-ts/lib/Option";

219

220

const ApiError = t.type({

221

code: t.string,

222

message: t.string,

223

details: option(t.string)

224

});

225

226

const UserData = t.type({

227

id: t.number,

228

name: t.string,

229

email: t.string

230

});

231

232

const ApiResponse = either(ApiError, UserData);

233

234

// Success response

235

const successResponse = {

236

_tag: "Right",

237

right: {

238

id: 1,

239

name: "Alice",

240

email: "alice@example.com"

241

}

242

};

243

244

const parsedSuccess = ApiResponse.decode(successResponse);

245

// Right(right({ id: 1, name: "Alice", email: "alice@example.com" }))

246

247

// Error response

248

const errorResponse = {

249

_tag: "Left",

250

left: {

251

code: "USER_NOT_FOUND",

252

message: "User not found",

253

details: { _tag: "Some", value: "User ID 123 does not exist" }

254

}

255

};

256

257

const parsedError = ApiResponse.decode(errorResponse);

258

// Right(left({ code: "USER_NOT_FOUND", message: "User not found", details: some("...") }))

259

```

260

261

### Optional Configuration Fields

262

263

```typescript

264

import * as t from "io-ts";

265

import { option, optionFromNullable } from "io-ts-types";

266

import * as O from "fp-ts/lib/Option";

267

268

const DatabaseConfig = t.type({

269

host: t.string,

270

port: t.number,

271

ssl: optionFromNullable(t.boolean), // null becomes None

272

timeout: option(t.number), // Explicit Option structure

273

credentials: optionFromNullable(t.type({

274

username: t.string,

275

password: t.string

276

}))

277

});

278

279

// Configuration with mixed optional fields

280

const config1 = {

281

host: "localhost",

282

port: 5432,

283

ssl: true, // Some(true)

284

timeout: { _tag: "None" }, // Explicit None

285

credentials: { // Some(credentials)

286

username: "admin",

287

password: "secret"

288

}

289

};

290

291

const parsed1 = DatabaseConfig.decode(config1);

292

293

// Configuration with null values

294

const config2 = {

295

host: "remote.db.com",

296

port: 5432,

297

ssl: null, // None (from null)

298

timeout: { _tag: "Some", value: 30000 }, // Explicit Some

299

credentials: null // None (from null)

300

};

301

302

const parsed2 = DatabaseConfig.decode(config2);

303

```

304

305

### Form Validation with Optional Fields

306

307

```typescript

308

import * as t from "io-ts";

309

import { optionFromNullable } from "io-ts-types";

310

import { pipe } from "fp-ts/lib/function";

311

import * as O from "fp-ts/lib/Option";

312

313

const UserProfile = t.type({

314

username: t.string,

315

email: t.string,

316

bio: optionFromNullable(t.string),

317

website: optionFromNullable(t.string),

318

avatar: optionFromNullable(t.string)

319

});

320

321

const formData = {

322

username: "alice_dev",

323

email: "alice@example.com",

324

bio: "Full-stack developer", // Some("Full-stack developer")

325

website: null, // None

326

avatar: undefined // None

327

};

328

329

const validated = UserProfile.decode(formData);

330

331

if (validated._tag === "Right") {

332

const profile = validated.right;

333

334

// Safe handling of optional fields

335

const displayBio = pipe(

336

profile.bio,

337

O.getOrElse(() => "No bio provided")

338

);

339

340

const hasWebsite = O.isSome(profile.website);

341

342

console.log(`Bio: ${displayBio}`); // Bio: Full-stack developer

343

console.log(`Has website: ${hasWebsite}`); // Has website: false

344

}

345

```

346

347

### Result Type Pattern

348

349

```typescript

350

import * as t from "io-ts";

351

import { either } from "io-ts-types";

352

import * as E from "fp-ts/lib/Either";

353

import { pipe } from "fp-ts/lib/function";

354

355

const ValidationError = t.type({

356

field: t.string,

357

message: t.string

358

});

359

360

const ProcessingResult = either(

361

t.array(ValidationError), // Left: array of validation errors

362

t.type({ // Right: success result

363

id: t.string,

364

processed: t.boolean,

365

timestamp: t.number

366

})

367

);

368

369

// Success case

370

const successData = {

371

_tag: "Right",

372

right: {

373

id: "task_123",

374

processed: true,

375

timestamp: 1703505000000

376

}

377

};

378

379

// Error case

380

const errorData = {

381

_tag: "Left",

382

left: [

383

{ field: "email", message: "Invalid email format" },

384

{ field: "age", message: "Must be a positive number" }

385

]

386

};

387

388

function handleResult(data: unknown) {

389

return pipe(

390

ProcessingResult.decode(data),

391

E.fold(

392

(decodeErrors) => `Decode error: ${decodeErrors}`,

393

E.fold(

394

(validationErrors) => `Validation failed: ${validationErrors.map(e => e.message).join(", ")}`,

395

(success) => `Processed successfully: ${success.id}`

396

)

397

)

398

);

399

}

400

401

const result1 = handleResult(successData);

402

// "Processed successfully: task_123"

403

404

const result2 = handleResult(errorData);

405

// "Validation failed: Invalid email format, Must be a positive number"

406

```

407

408

### Database Query Results

409

410

```typescript

411

import * as t from "io-ts";

412

import { option, either } from "io-ts-types";

413

import * as O from "fp-ts/lib/Option";

414

import * as E from "fp-ts/lib/Either";

415

416

const User = t.type({

417

id: t.number,

418

name: t.string,

419

lastLogin: option(t.string) // Some(date) or None if never logged in

420

});

421

422

const DatabaseError = t.type({

423

code: t.string,

424

query: t.string

425

});

426

427

const QueryResult = either(DatabaseError, option(User));

428

429

// User found

430

const userFound = {

431

_tag: "Right",

432

right: {

433

_tag: "Some",

434

value: {

435

id: 1,

436

name: "Alice",

437

lastLogin: { _tag: "Some", value: "2023-12-25T10:30:00Z" }

438

}

439

}

440

};

441

442

// User not found (query succeeded, but no result)

443

const userNotFound = {

444

_tag: "Right",

445

right: { _tag: "None" }

446

};

447

448

// Database error

449

const dbError = {

450

_tag: "Left",

451

left: {

452

code: "CONNECTION_TIMEOUT",

453

query: "SELECT * FROM users WHERE id = 1"

454

}

455

};

456

457

function processQueryResult(result: unknown) {

458

return pipe(

459

QueryResult.decode(result),

460

E.fold(

461

(errors) => `Invalid result format: ${errors}`,

462

E.fold(

463

(dbErr) => `Database error ${dbErr.code}: ${dbErr.query}`,

464

O.fold(

465

() => "User not found",

466

(user) => `Found user: ${user.name} (ID: ${user.id})`

467

)

468

)

469

)

470

);

471

}

472

```