0
# Schema & Type Classes
1
2
Advanced schema-based type construction and functional programming abstractions for maximum composability.
3
4
## Capabilities
5
6
### Schema Interface
7
8
The core Schema interface for creating reusable, composable type definitions.
9
10
```typescript { .api }
11
/**
12
* Schema interface for creating composable type definitions
13
* @template A - The runtime type
14
*/
15
interface Schema<A> {
16
<S>(S: Schemable<S>): HKT<S, A>;
17
}
18
19
/** Extract type from Schema */
20
type TypeOf<S> = S extends Schema<infer A> ? A : never;
21
```
22
23
### Schema Constructor
24
25
Function for creating memoized schemas.
26
27
```typescript { .api }
28
/**
29
* Create a memoized schema
30
* @param schema - The schema function
31
*/
32
function make<A>(schema: Schema<A>): Schema<A>;
33
34
/**
35
* Interpret a schema with a specific Schemable instance
36
* @param S - The Schemable instance
37
*/
38
function interpreter<S>(S: Schemable<S>): <A>(schema: Schema<A>) => HKT<S, A>;
39
```
40
41
**Usage Example:**
42
43
```typescript
44
import * as S from "io-ts/Schema";
45
import * as D from "io-ts/Decoder";
46
import * as G from "io-ts/Guard";
47
48
// Create a reusable schema
49
const PersonSchema = S.make(S =>
50
S.struct({
51
name: S.string,
52
age: S.number,
53
email: S.string
54
})
55
);
56
57
// Interpret schema as different implementations
58
const PersonDecoder = S.interpreter(D.Schemable)(PersonSchema);
59
const PersonGuard = S.interpreter(G.Schemable)(PersonSchema);
60
61
// Use the generated decoder and guard
62
const result = PersonDecoder.decode({ name: "Alice", age: 30, email: "alice@example.com" });
63
const isValid = PersonGuard.is({ name: "Bob", age: 25, email: "bob@example.com" });
64
```
65
66
### Schemable Type Classes
67
68
Core type classes that define the capability interfaces for different implementations.
69
70
```typescript { .api }
71
/** Literal value types */
72
type Literal = string | number | boolean | null;
73
74
/**
75
* Base Schemable interface for type-level programming
76
* @template S - The HKT parameter
77
*/
78
interface Schemable<S> {
79
readonly URI: S;
80
readonly literal: <A extends [Literal, ...Array<Literal>]>(...values: A) => HKT<S, A[number]>;
81
readonly string: HKT<S, string>;
82
readonly number: HKT<S, number>;
83
readonly boolean: HKT<S, boolean>;
84
readonly nullable: <A>(or: HKT<S, A>) => HKT<S, A | null>;
85
readonly struct: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A>;
86
readonly partial: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, Partial<A>>;
87
readonly record: <A>(codomain: HKT<S, A>) => HKT<S, Record<string, A>>;
88
readonly array: <A>(item: HKT<S, A>) => HKT<S, Array<A>>;
89
readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A>;
90
readonly intersect: <B>(right: HKT<S, B>) => <A>(left: HKT<S, A>) => HKT<S, A & B>;
91
readonly lazy: <A>(f: () => HKT<S, A>) => HKT<S, A>;
92
readonly readonly: <A>(inner: HKT<S, A>) => HKT<S, Readonly<A>>;
93
}
94
95
/**
96
* Schemable interface for URIS (Higher-Kinded Types with single parameter)
97
*/
98
interface Schemable1<S extends URIS> {
99
readonly URI: S;
100
readonly literal: <A extends [Literal, ...Array<Literal>]>(...values: A) => Kind<S, A[number]>;
101
readonly string: Kind<S, string>;
102
readonly number: Kind<S, number>;
103
readonly boolean: Kind<S, boolean>;
104
readonly nullable: <A>(or: Kind<S, A>) => Kind<S, A | null>;
105
readonly struct: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A>;
106
readonly partial: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, Partial<A>>;
107
readonly record: <A>(codomain: Kind<S, A>) => Kind<S, Record<string, A>>;
108
readonly array: <A>(item: Kind<S, A>) => Kind<S, Array<A>>;
109
readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A>;
110
readonly intersect: <B>(right: Kind<S, B>) => <A>(left: Kind<S, A>) => Kind<S, A & B>;
111
readonly lazy: <A>(f: () => Kind<S, A>) => Kind<S, A>;
112
readonly readonly: <A>(inner: Kind<S, A>) => Kind<S, Readonly<A>>;
113
}
114
115
/**
116
* Schemable interface for URIS2 with error parameter (Higher-Kinded Types with two parameters)
117
*/
118
interface Schemable2C<S extends URIS2, E> {
119
readonly URI: S;
120
readonly _E: E;
121
readonly literal: <A extends [Literal, ...Array<Literal>]>(...values: A) => Kind2<S, E, A[number]>;
122
readonly string: Kind2<S, E, string>;
123
readonly number: Kind2<S, E, number>;
124
readonly boolean: Kind2<S, E, boolean>;
125
readonly nullable: <A>(or: Kind2<S, E, A>) => Kind2<S, E, A | null>;
126
readonly struct: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, A>;
127
readonly partial: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, Partial<A>>;
128
readonly record: <A>(codomain: Kind2<S, E, A>) => Kind2<S, E, Record<string, A>>;
129
readonly array: <A>(item: Kind2<S, E, A>) => Kind2<S, E, Array<A>>;
130
readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, A>;
131
readonly intersect: <B>(right: Kind2<S, E, B>) => <A>(left: Kind2<S, E, A>) => Kind2<S, E, A & B>;
132
readonly lazy: <A>(f: () => Kind2<S, E, A>) => Kind2<S, E, A>;
133
readonly readonly: <A>(inner: Kind2<S, E, A>) => Kind2<S, E, Readonly<A>>;
134
}
135
```
136
137
### Extended Schemable Interfaces
138
139
Additional interfaces for unknown containers, unions, and refinements.
140
141
```typescript { .api }
142
/**
143
* Schemable interface with unknown container support
144
*/
145
interface WithUnknownContainers<S> {
146
readonly UnknownArray: HKT<S, Array<unknown>>;
147
readonly UnknownRecord: HKT<S, Record<string, unknown>>;
148
}
149
150
interface WithUnknownContainers1<S extends URIS> {
151
readonly UnknownArray: Kind<S, Array<unknown>>;
152
readonly UnknownRecord: Kind<S, Record<string, unknown>>;
153
}
154
155
interface WithUnknownContainers2C<S extends URIS2, E> {
156
readonly UnknownArray: Kind2<S, E, Array<unknown>>;
157
readonly UnknownRecord: Kind2<S, E, Record<string, unknown>>;
158
}
159
160
/**
161
* Schemable interface with union support
162
*/
163
interface WithUnion<S> {
164
readonly union: <MS extends [HKT<S, any>, HKT<S, any>, ...Array<HKT<S, any>>]>(...members: MS) => HKT<S, TypeOf<MS[number]>>;
165
}
166
167
interface WithUnion1<S extends URIS> {
168
readonly union: <MS extends [Kind<S, any>, Kind<S, any>, ...Array<Kind<S, any>>]>(...members: MS) => Kind<S, TypeOf<MS[number]>>;
169
}
170
171
interface WithUnion2C<S extends URIS2, E> {
172
readonly union: <MS extends [Kind2<S, E, any>, Kind2<S, E, any>, ...Array<Kind2<S, E, any>>]>(...members: MS) => Kind2<S, E, TypeOf<MS[number]>>;
173
}
174
175
/**
176
* Schemable interface with refinement support
177
*/
178
interface WithRefine<S> {
179
readonly refine: <A, B extends A>(refinement: Refinement<A, B>, id: string) => (from: HKT<S, A>) => HKT<S, B>;
180
}
181
182
interface WithRefine1<S extends URIS> {
183
readonly refine: <A, B extends A>(refinement: Refinement<A, B>, id: string) => (from: Kind<S, A>) => Kind<S, B>;
184
}
185
186
interface WithRefine2C<S extends URIS2, E> {
187
readonly refine: <A, B extends A>(refinement: Refinement<A, B>, id: string) => (from: Kind2<S, E, A>) => Kind2<S, E, B>;
188
}
189
```
190
191
### Utility Functions
192
193
Helper functions for schema operations.
194
195
```typescript { .api }
196
/**
197
* Memoization utility for expensive computations
198
* @param f - Function to memoize
199
*/
200
function memoize<A, B>(f: (a: A) => B): (a: A) => B;
201
202
/**
203
* Internal intersection utility
204
* @param a - First value
205
* @param b - Second value
206
*/
207
function intersect_<A, B>(a: A, b: B): A & B;
208
```
209
210
## Advanced Usage Patterns
211
212
### Multi-Implementation Schema
213
214
```typescript
215
import * as S from "io-ts/Schema";
216
import * as D from "io-ts/Decoder";
217
import * as G from "io-ts/Guard";
218
import * as Eq from "io-ts/Eq";
219
220
// Define a schema once
221
const UserSchema = S.make(S =>
222
S.struct({
223
id: S.number,
224
name: S.string,
225
email: S.string,
226
age: S.number,
227
isActive: S.boolean
228
})
229
);
230
231
// Generate multiple implementations
232
const UserDecoder = S.interpreter(D.Schemable)(UserSchema);
233
const UserGuard = S.interpreter(G.Schemable)(UserSchema);
234
const UserEq = S.interpreter(Eq.Schemable)(UserSchema);
235
236
// Use each implementation for its purpose
237
const userData = { id: 1, name: "Alice", email: "alice@example.com", age: 30, isActive: true };
238
239
// Decode untrusted data
240
const decodeResult = UserDecoder.decode(userData);
241
242
// Type guard for runtime checks
243
if (UserGuard.is(userData)) {
244
console.log("Valid user data");
245
}
246
247
// Compare users for equality
248
const user1 = userData;
249
const user2 = { ...userData, name: "Bob" };
250
const areEqual = UserEq.equals(user1, user2); // false
251
```
252
253
### Complex Nested Schema
254
255
```typescript
256
import * as S from "io-ts/Schema";
257
258
const AddressSchema = S.make(S =>
259
S.struct({
260
street: S.string,
261
city: S.string,
262
country: S.string,
263
postalCode: S.string
264
})
265
);
266
267
const CompanySchema = S.make(S =>
268
S.struct({
269
name: S.string,
270
industry: S.string,
271
employees: S.number,
272
address: S.interpreter(S)(AddressSchema)
273
})
274
);
275
276
const PersonSchema = S.make(S =>
277
S.struct({
278
id: S.number,
279
profile: S.struct({
280
firstName: S.string,
281
lastName: S.string,
282
email: S.string
283
}),
284
addresses: S.array(S.interpreter(S)(AddressSchema)),
285
company: S.nullable(S.interpreter(S)(CompanySchema)),
286
preferences: S.partial({
287
theme: S.union(S.literal('light'), S.literal('dark')),
288
notifications: S.boolean,
289
language: S.string
290
})
291
})
292
);
293
294
// Generate decoder for the complex nested structure
295
const PersonDecoder = S.interpreter(D.Schemable)(PersonSchema);
296
```
297
298
### Recursive Schema
299
300
```typescript
301
import * as S from "io-ts/Schema";
302
303
interface TreeNode {
304
value: number;
305
children: TreeNode[];
306
}
307
308
const TreeSchema: S.Schema<TreeNode> = S.make(S =>
309
S.lazy(() =>
310
S.struct({
311
value: S.number,
312
children: S.array(S.interpreter(S)(TreeSchema))
313
})
314
)
315
);
316
317
const TreeDecoder = S.interpreter(D.Schemable)(TreeSchema);
318
319
const treeData = {
320
value: 1,
321
children: [
322
{ value: 2, children: [] },
323
{ value: 3, children: [{ value: 4, children: [] }] }
324
]
325
};
326
327
const result = TreeDecoder.decode(treeData);
328
```
329
330
### Schema with Refinements
331
332
```typescript
333
import * as S from "io-ts/Schema";
334
import * as D from "io-ts/Decoder";
335
336
const EmailSchema = S.make(S =>
337
S.refine(
338
(s: string): s is string => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s),
339
'Email'
340
)(S.string)
341
);
342
343
const PositiveNumberSchema = S.make(S =>
344
S.refine(
345
(n: number): n is number => n > 0,
346
'PositiveNumber'
347
)(S.number)
348
);
349
350
const ValidatedUserSchema = S.make(S =>
351
S.struct({
352
name: S.refine(
353
(s: string): s is string => s.length > 0,
354
'NonEmptyString'
355
)(S.string),
356
email: S.interpreter(S)(EmailSchema),
357
age: S.interpreter(S)(PositiveNumberSchema)
358
})
359
);
360
361
const ValidatedUserDecoder = S.interpreter(D.Schemable)(ValidatedUserSchema);
362
```