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

collection-codecs.mddocs/

0

# Collection Codecs

1

2

Codecs for working with arrays, sets, and maps including non-empty arrays and proper serialization of complex collection types. These codecs enable type-safe handling of collections with specialized constraints and serialization formats.

3

4

## Capabilities

5

6

### NonEmptyArray

7

8

Creates codecs for fp-ts NonEmptyArray types that ensure arrays always contain at least one element.

9

10

```typescript { .api }

11

/**

12

* Type interface for NonEmptyArray codec

13

*/

14

interface NonEmptyArrayC<C extends t.Mixed>

15

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

16

17

/**

18

* Creates a codec for NonEmptyArray<A> that validates arrays are non-empty

19

* @param codec - The codec for individual array elements

20

* @param name - Optional name for the codec

21

* @returns NonEmptyArray codec that ensures at least one element

22

*/

23

function nonEmptyArray<C extends t.Mixed>(

24

codec: C,

25

name?: string

26

): NonEmptyArrayC<C>;

27

```

28

29

**Usage Examples:**

30

31

```typescript

32

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

33

import * as t from "io-ts";

34

import { NonEmptyArray } from "fp-ts/lib/NonEmptyArray";

35

36

// Create codec for non-empty array of strings

37

const NonEmptyStringArray = nonEmptyArray(t.string);

38

39

const result1 = NonEmptyStringArray.decode(["hello", "world"]);

40

// Right(NonEmptyArray<string>)

41

42

const result2 = NonEmptyStringArray.decode(["single"]);

43

// Right(NonEmptyArray<string>)

44

45

const result3 = NonEmptyStringArray.decode([]);

46

// Left([ValidationError]) - empty array not allowed

47

48

const result4 = NonEmptyStringArray.decode(["hello", 123]);

49

// Left([ValidationError]) - invalid element type

50

51

// Encoding back to regular array

52

const nonEmptyArray = result1.right;

53

const encoded = NonEmptyStringArray.encode(nonEmptyArray);

54

// ["hello", "world"]

55

```

56

57

### ReadonlyNonEmptyArray

58

59

Creates codecs for readonly non-empty arrays, providing immutable collection guarantees.

60

61

```typescript { .api }

62

/**

63

* Interface for readonly non-empty arrays

64

*/

65

interface ReadonlyNonEmptyArray<A> extends ReadonlyArray<A> {

66

readonly 0: A;

67

}

68

69

/**

70

* Type interface for ReadonlyNonEmptyArray codec

71

*/

72

interface ReadonlyNonEmptyArrayC<C extends t.Mixed>

73

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

74

75

/**

76

* Creates a codec for ReadonlyNonEmptyArray<A>

77

* @param codec - The codec for individual array elements

78

* @param name - Optional name for the codec

79

* @returns ReadonlyNonEmptyArray codec

80

*/

81

function readonlyNonEmptyArray<C extends t.Mixed>(

82

codec: C,

83

name?: string

84

): ReadonlyNonEmptyArrayC<C>;

85

```

86

87

**Usage Examples:**

88

89

```typescript

90

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

91

import * as t from "io-ts";

92

93

const ReadonlyNumbers = readonlyNonEmptyArray(t.number);

94

95

const result1 = ReadonlyNumbers.decode([1, 2, 3]);

96

// Right(ReadonlyNonEmptyArray<number>)

97

98

const result2 = ReadonlyNumbers.decode([]);

99

// Left([ValidationError]) - empty array

100

101

// The result is readonly - no mutation methods available

102

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

103

const numbers = result1.right;

104

const first = numbers[0]; // 1 (guaranteed to exist)

105

// numbers.push(4); // TypeScript error - readonly array

106

}

107

```

108

109

### Set from Array

110

111

Creates codecs for Set types that serialize as arrays, with uniqueness validation and ordering support.

112

113

```typescript { .api }

114

/**

115

* Type interface for Set codec

116

*/

117

interface SetFromArrayC<C extends t.Mixed>

118

extends t.Type<Set<t.TypeOf<C>>, Array<t.OutputOf<C>>, unknown> {}

119

120

/**

121

* Creates a codec for Set<A> that serializes as an array

122

* @param codec - The codec for individual set elements

123

* @param O - Ord instance for element comparison and ordering

124

* @param name - Optional name for the codec

125

* @returns Set codec that validates uniqueness and requires Ord instance

126

*/

127

function setFromArray<C extends t.Mixed>(

128

codec: C,

129

O: Ord<t.TypeOf<C>>,

130

name?: string

131

): SetFromArrayC<C>;

132

```

133

134

**Usage Examples:**

135

136

```typescript

137

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

138

import * as t from "io-ts";

139

import { Ord } from "fp-ts/lib/Ord";

140

import * as S from "fp-ts/lib/string";

141

import * as N from "fp-ts/lib/number";

142

143

// String set with string ordering

144

const StringSet = setFromArray(t.string, S.Ord);

145

146

const result1 = StringSet.decode(["apple", "banana", "cherry"]);

147

// Right(Set<string>)

148

149

const result2 = StringSet.decode(["apple", "banana", "apple"]);

150

// Right(Set<string>) - duplicates removed

151

152

const result3 = StringSet.decode([]);

153

// Right(Set<string>) - empty set is valid

154

155

// Number set with number ordering

156

const NumberSet = setFromArray(t.number, N.Ord);

157

158

const result4 = NumberSet.decode([3, 1, 4, 1, 5]);

159

// Right(Set<number>) - duplicates removed, order determined by Ord

160

161

// Encoding back to array (ordered by Ord)

162

const stringSet = new Set(["zebra", "apple", "banana"]);

163

const encoded = StringSet.encode(stringSet);

164

// ["apple", "banana", "zebra"] - sorted by string Ord

165

```

166

167

### ReadonlySet from Array

168

169

Creates codecs for ReadonlySet types with immutable guarantees.

170

171

```typescript { .api }

172

/**

173

* Type interface for ReadonlySet codec

174

*/

175

interface ReadonlySetFromArrayC<C extends t.Mixed>

176

extends t.Type<ReadonlySet<t.TypeOf<C>>, ReadonlyArray<t.OutputOf<C>>, unknown> {}

177

178

/**

179

* Creates a codec for ReadonlySet<A>

180

* @param codec - The codec for individual set elements

181

* @param O - Ord instance for element comparison and ordering

182

* @param name - Optional name for the codec

183

* @returns ReadonlySet codec

184

*/

185

function readonlySetFromArray<C extends t.Mixed>(

186

codec: C,

187

O: Ord<t.TypeOf<C>>,

188

name?: string

189

): ReadonlySetFromArrayC<C>;

190

```

191

192

### Map from Entries

193

194

Creates codecs for Map types that serialize as arrays of key-value pairs.

195

196

```typescript { .api }

197

/**

198

* Type interface for Map codec

199

*/

200

interface MapFromEntriesC<K extends t.Mixed, V extends t.Mixed>

201

extends t.Type<Map<t.TypeOf<K>, t.TypeOf<V>>, Array<[t.OutputOf<K>, t.OutputOf<V>]>, unknown> {}

202

203

/**

204

* Creates a codec for Map<K, V> that serializes as an array of key-value pairs

205

* @param keyCodec - The codec for map keys

206

* @param KO - Ord instance for key comparison and ordering

207

* @param valueCodec - The codec for map values

208

* @param name - Optional name for the codec

209

* @returns Map codec that requires Ord instance for keys

210

*/

211

function mapFromEntries<K extends t.Mixed, V extends t.Mixed>(

212

keyCodec: K,

213

KO: Ord<t.TypeOf<K>>,

214

valueCodec: V,

215

name?: string

216

): MapFromEntriesC<K, V>;

217

```

218

219

**Usage Examples:**

220

221

```typescript

222

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

223

import * as t from "io-ts";

224

import * as S from "fp-ts/lib/string";

225

import * as N from "fp-ts/lib/number";

226

227

// Map with string keys and number values

228

const StringNumberMap = mapFromEntries(t.string, S.Ord, t.number);

229

230

const result1 = StringNumberMap.decode([

231

["apple", 5],

232

["banana", 3],

233

["cherry", 8]

234

]);

235

// Right(Map<string, number>)

236

237

const result2 = StringNumberMap.decode([]);

238

// Right(Map<string, number>) - empty map is valid

239

240

const result3 = StringNumberMap.decode([

241

["apple", 5],

242

["apple", 10] // Duplicate key - last value wins

243

]);

244

// Right(Map<string, number>) - Map with apple -> 10

245

246

// Invalid value type

247

const result4 = StringNumberMap.decode([

248

["apple", "not-a-number"]

249

]);

250

// Left([ValidationError])

251

252

// Encoding back to entries array

253

const map = new Map([["zebra", 1], ["apple", 2], ["banana", 3]]);

254

const encoded = StringNumberMap.encode(map);

255

// [["apple", 2], ["banana", 3], ["zebra", 1]] - sorted by key Ord

256

```

257

258

### ReadonlyMap from Entries

259

260

Creates codecs for ReadonlyMap types with immutable guarantees.

261

262

```typescript { .api }

263

/**

264

* Type interface for ReadonlyMap codec

265

*/

266

interface ReadonlyMapFromEntriesC<K extends t.Mixed, V extends t.Mixed>

267

extends t.Type<ReadonlyMap<t.TypeOf<K>, t.TypeOf<V>>, ReadonlyArray<[t.OutputOf<K>, t.OutputOf<V>]>, unknown> {}

268

269

/**

270

* Creates a codec for ReadonlyMap<K, V>

271

* @param keyCodec - The codec for map keys

272

* @param KO - Ord instance for key comparison and ordering

273

* @param valueCodec - The codec for map values

274

* @param name - Optional name for the codec

275

* @returns ReadonlyMap codec

276

*/

277

function readonlyMapFromEntries<K extends t.Mixed, V extends t.Mixed>(

278

keyCodec: K,

279

KO: Ord<t.TypeOf<K>>,

280

valueCodec: V,

281

name?: string

282

): ReadonlyMapFromEntriesC<K, V>;

283

```

284

285

## Common Usage Patterns

286

287

### API Response with Collections

288

289

```typescript

290

import * as t from "io-ts";

291

import { nonEmptyArray, setFromArray, mapFromEntries } from "io-ts-types";

292

import * as S from "fp-ts/lib/string";

293

import * as N from "fp-ts/lib/number";

294

295

const User = t.type({

296

id: t.number,

297

name: t.string,

298

email: t.string

299

});

300

301

const Project = t.type({

302

id: t.number,

303

name: t.string,

304

members: nonEmptyArray(User), // At least one member required

305

tags: setFromArray(t.string, S.Ord), // Unique tags

306

metadata: mapFromEntries(t.string, S.Ord, t.string) // Key-value metadata

307

});

308

309

const projectData = {

310

id: 1,

311

name: "Web Application",

312

members: [

313

{ id: 1, name: "Alice", email: "alice@example.com" },

314

{ id: 2, name: "Bob", email: "bob@example.com" }

315

],

316

tags: ["typescript", "react", "nodejs", "react"], // Duplicate removed

317

metadata: [

318

["repository", "https://github.com/org/project"],

319

["status", "active"],

320

["priority", "high"]

321

]

322

};

323

324

const parsed = Project.decode(projectData);

325

// Right({

326

// id: 1,

327

// name: "Web Application",

328

// members: NonEmptyArray<User>,

329

// tags: Set<string>,

330

// metadata: Map<string, string>

331

// })

332

```

333

334

### Configuration with Collections

335

336

```typescript

337

import * as t from "io-ts";

338

import { setFromArray, mapFromEntries, nonEmptyArray } from "io-ts-types";

339

import * as S from "fp-ts/lib/string";

340

import * as N from "fp-ts/lib/number";

341

342

const ServerConfig = t.type({

343

hosts: nonEmptyArray(t.string), // At least one host

344

allowedOrigins: setFromArray(t.string, S.Ord), // Unique origins

345

ports: setFromArray(t.number, N.Ord), // Unique ports

346

environment: mapFromEntries(t.string, S.Ord, t.string) // Env variables

347

});

348

349

const config = {

350

hosts: ["api.example.com", "api2.example.com"],

351

allowedOrigins: ["https://app.example.com", "https://admin.example.com"],

352

ports: [8080, 8443, 9000],

353

environment: [

354

["NODE_ENV", "production"],

355

["LOG_LEVEL", "info"],

356

["DATABASE_URL", "postgresql://..."]

357

]

358

};

359

360

const validated = ServerConfig.decode(config);

361

// Right({ hosts: NonEmptyArray, allowedOrigins: Set, ports: Set, environment: Map })

362

```

363

364

### Data Processing Pipeline

365

366

```typescript

367

import * as t from "io-ts";

368

import { nonEmptyArray, setFromArray } from "io-ts-types";

369

import * as S from "fp-ts/lib/string";

370

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

371

import * as NEA from "fp-ts/lib/NonEmptyArray";

372

373

const ProcessingJob = t.type({

374

id: t.string,

375

inputFiles: nonEmptyArray(t.string), // Must have input files

376

outputFormats: setFromArray(t.string, S.Ord), // Unique output formats

377

steps: nonEmptyArray(t.string) // Must have processing steps

378

});

379

380

const jobData = {

381

id: "job_001",

382

inputFiles: ["data.csv", "metadata.json"],

383

outputFormats: ["json", "csv", "xml", "json"], // Duplicate removed

384

steps: ["validate", "transform", "aggregate", "export"]

385

};

386

387

const job = ProcessingJob.decode(jobData);

388

389

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

390

const { inputFiles, outputFormats, steps } = job.right;

391

392

// Safe operations on non-empty arrays

393

const firstFile = NEA.head(inputFiles); // "data.csv"

394

const totalSteps = steps.length; // 4

395

396

// Safe operations on sets

397

const hasJsonOutput = outputFormats.has("json"); // true

398

const formatCount = outputFormats.size; // 3 (duplicates removed)

399

400

console.log(`Processing ${totalSteps} steps for ${inputFiles.length} files`);

401

console.log(`Generating ${formatCount} output formats`);

402

}

403

```

404

405

### Database Entity with Collections

406

407

```typescript

408

import * as t from "io-ts";

409

import { nonEmptyArray, setFromArray, mapFromEntries } from "io-ts-types";

410

import * as S from "fp-ts/lib/string";

411

import * as N from "fp-ts/lib/number";

412

413

const Category = t.type({

414

id: t.number,

415

name: t.string

416

});

417

418

const Article = t.type({

419

id: t.number,

420

title: t.string,

421

content: t.string,

422

categories: nonEmptyArray(Category), // Must be in at least one category

423

tags: setFromArray(t.string, S.Ord), // Unique tags

424

metadata: mapFromEntries(t.string, S.Ord, t.string), // Custom metadata

425

relatedArticles: setFromArray(t.number, N.Ord) // Unique article IDs

426

});

427

428

const articleData = {

429

id: 1,

430

title: "Getting Started with TypeScript",

431

content: "TypeScript is a typed superset of JavaScript...",

432

categories: [

433

{ id: 1, name: "Programming" },

434

{ id: 2, name: "TypeScript" }

435

],

436

tags: ["typescript", "javascript", "tutorial", "beginners"],

437

metadata: [

438

["author", "Alice Developer"],

439

["publishDate", "2023-12-25"],

440

["readTime", "10 minutes"]

441

],

442

relatedArticles: [5, 12, 8, 15]

443

};

444

445

const parsed = Article.decode(articleData);

446

// Right({

447

// id: 1,

448

// title: "...",

449

// content: "...",

450

// categories: NonEmptyArray<Category>,

451

// tags: Set<string>,

452

// metadata: Map<string, string>,

453

// relatedArticles: Set<number>

454

// })

455

```

456

457

### Form Processing with Collections

458

459

```typescript

460

import * as t from "io-ts";

461

import { nonEmptyArray, setFromArray } from "io-ts-types";

462

import * as S from "fp-ts/lib/string";

463

464

const SurveyResponse = t.type({

465

respondentId: t.string,

466

answers: nonEmptyArray(t.string), // Must answer at least one question

467

selectedOptions: setFromArray(t.string, S.Ord), // Unique selected options

468

feedback: t.union([t.string, t.null])

469

});

470

471

// Form data with repeated selections

472

const responseData = {

473

respondentId: "user_123",

474

answers: ["Satisfied", "Very Good", "Yes"],

475

selectedOptions: ["option_a", "option_c", "option_b", "option_a"], // Duplicates

476

feedback: "Great survey!"

477

};

478

479

const validated = SurveyResponse.decode(responseData);

480

481

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

482

const response = validated.right;

483

484

// selectedOptions is now a Set with duplicates removed

485

const uniqueSelections = Array.from(response.selectedOptions);

486

// ["option_a", "option_b", "option_c"] - sorted by string Ord

487

488

console.log(`Respondent ${response.respondentId} provided ${response.answers.length} answers`);

489

console.log(`Selected ${response.selectedOptions.size} unique options`);

490

}

491

```