or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

composite.mdconstraints.mdcontracts.mdindex.mdliterals.mdprimitives.mdresults.mdtemplates.mdunion-intersect.mdutilities.mdvalidation.md

contracts.mddocs/

0

# Function Contracts

1

2

Runtime validation for function arguments and return values. Contracts ensure type safety at function boundaries, making APIs more robust and providing clear runtime error messages.

3

4

## Capabilities

5

6

### Contract

7

8

Creates function contracts for runtime argument and return value validation.

9

10

```typescript { .api }

11

/**

12

* Creates a function contract with optional argument and return validation

13

* @param options - Contract configuration

14

* @example Contract({ receives: Tuple(String, Number), returns: Boolean })

15

*/

16

function Contract<O extends ContractOptions>(options: O): ContractType<O>;

17

18

interface ContractOptions {

19

receives?: Runtype<readonly unknown[]>;

20

returns?: Runtype;

21

}

22

23

interface ContractType<O> {

24

receives?: O["receives"];

25

returns?: O["returns"];

26

enforce<F extends Function>(fn: F): EnforcedFunction<O, F>;

27

}

28

```

29

30

**Usage Examples:**

31

32

```typescript

33

import { Contract, String, Number, Boolean, Tuple } from "runtypes";

34

35

// Basic function contract

36

const AddContract = Contract({

37

receives: Tuple(Number, Number),

38

returns: Number

39

});

40

41

// Wrap function with contract

42

const safeAdd = AddContract.enforce((a: number, b: number) => a + b);

43

44

// Usage - validates arguments and return value

45

const result = safeAdd(5, 3); // 8

46

safeAdd("5", 3); // throws ValidationError - invalid arguments

47

48

// String processing contract

49

const StringProcessContract = Contract({

50

receives: Tuple(String),

51

returns: String

52

});

53

54

const processString = StringProcessContract.enforce((s: string) => s.toUpperCase());

55

const processed = processString("hello"); // "HELLO"

56

```

57

58

### Argument Validation Only

59

60

```typescript

61

import { Contract, Array, String, Object, Number } from "runtypes";

62

63

// Validate only arguments

64

const LogContract = Contract({

65

receives: Tuple(String, String) // level, message

66

});

67

68

const log = LogContract.enforce((level: string, message: string) => {

69

console.log(`[${level}] ${message}`);

70

});

71

72

log("INFO", "Application started"); // OK

73

log("INFO"); // throws ValidationError - missing argument

74

75

// Variable argument validation

76

const SumContract = Contract({

77

receives: Array(Number)

78

});

79

80

const sum = SumContract.enforce((...numbers: number[]) =>

81

numbers.reduce((acc, n) => acc + n, 0)

82

);

83

84

const total = sum(1, 2, 3, 4, 5); // 15

85

```

86

87

### Return Value Validation Only

88

89

```typescript

90

import { Contract, String, Number, Object } from "runtypes";

91

92

// Validate only return value

93

const UserFactoryContract = Contract({

94

returns: Object({

95

id: Number,

96

name: String,

97

email: String

98

})

99

});

100

101

const createUser = UserFactoryContract.enforce(() => ({

102

id: 1,

103

name: "Alice",

104

email: "alice@example.com"

105

}));

106

107

const user = createUser(); // validated return value

108

109

// Contract catches bugs in implementation

110

const buggyCreateUser = UserFactoryContract.enforce(() => ({

111

id: "1", // wrong type - will throw ValidationError

112

name: "Alice",

113

email: "alice@example.com"

114

}));

115

```

116

117

### AsyncContract

118

119

Creates contracts for async functions with Promise validation.

120

121

```typescript { .api }

122

/**

123

* Creates a contract for async functions

124

* @param options - Async contract configuration

125

* @example AsyncContract({ receives: Tuple(String), resolves: Object({...}) })

126

*/

127

function AsyncContract<O extends AsyncContractOptions>(options: O): AsyncContractType<O>;

128

129

interface AsyncContractOptions {

130

receives?: Runtype<readonly unknown[]>;

131

returns?: Runtype;

132

resolves?: Runtype;

133

}

134

135

interface AsyncContractType<O> {

136

receives?: O["receives"];

137

returns?: O["returns"];

138

resolves?: O["resolves"];

139

enforce<F extends AsyncFunction>(fn: F): EnforcedAsyncFunction<O, F>;

140

}

141

```

142

143

**Usage Examples:**

144

145

```typescript

146

import { AsyncContract, String, Number, Object, Array } from "runtypes";

147

148

// API call contract

149

const FetchUserContract = AsyncContract({

150

receives: Tuple(Number), // user ID

151

resolves: Object({ // resolved value validation

152

id: Number,

153

name: String,

154

email: String

155

})

156

});

157

158

const fetchUser = FetchUserContract.enforce(async (id: number) => {

159

const response = await fetch(`/api/users/${id}`);

160

const data = await response.json();

161

return data; // validated against resolves runtype

162

});

163

164

// Usage

165

const user = await fetchUser(123); // Promise<{id: number, name: string, email: string}>

166

167

// Database operation contract

168

const SaveUserContract = AsyncContract({

169

receives: Tuple(Object({

170

name: String,

171

email: String

172

})),

173

resolves: Object({

174

id: Number,

175

name: String,

176

email: String,

177

createdAt: String

178

})

179

});

180

181

const saveUser = SaveUserContract.enforce(async (userData) => {

182

// Database save operation

183

const savedUser = await db.users.create(userData);

184

return savedUser;

185

});

186

```

187

188

## Advanced Contract Patterns

189

190

### Method Contracts

191

192

```typescript

193

import { Contract, String, Number, Object, Boolean } from "runtypes";

194

195

class UserService {

196

private users: Map<number, any> = new Map();

197

198

// Contract for instance methods

199

getUserById = Contract({

200

receives: Tuple(Number),

201

returns: Object({

202

id: Number,

203

name: String,

204

active: Boolean

205

}).optional() // might return undefined

206

}).enforce((id: number) => {

207

return this.users.get(id);

208

});

209

210

createUser = Contract({

211

receives: Tuple(Object({

212

name: String,

213

email: String

214

})),

215

returns: Object({

216

id: Number,

217

name: String,

218

email: String,

219

active: Boolean

220

})

221

}).enforce((userData) => {

222

const id = this.users.size + 1;

223

const user = { ...userData, id, active: true };

224

this.users.set(id, user);

225

return user;

226

});

227

}

228

229

// Usage

230

const service = new UserService();

231

const user = service.createUser({ name: "Alice", email: "alice@example.com" });

232

const retrieved = service.getUserById(user.id);

233

```

234

235

### API Endpoint Contracts

236

237

```typescript

238

import { Contract, AsyncContract, String, Number, Object, Array, Union, Literal } from "runtypes";

239

240

// Request/Response contracts for API endpoints

241

const CreatePostContract = AsyncContract({

242

receives: Tuple(Object({

243

title: String,

244

content: String,

245

authorId: Number

246

})),

247

resolves: Object({

248

id: Number,

249

title: String,

250

content: String,

251

authorId: Number,

252

createdAt: String,

253

status: Union(Literal("draft"), Literal("published"))

254

})

255

});

256

257

const GetPostsContract = AsyncContract({

258

receives: Tuple(Object({

259

page: Number.optional(),

260

limit: Number.optional(),

261

authorId: Number.optional()

262

}).optional()),

263

resolves: Object({

264

posts: Array(Object({

265

id: Number,

266

title: String,

267

excerpt: String,

268

authorId: Number,

269

createdAt: String

270

})),

271

pagination: Object({

272

page: Number,

273

limit: Number,

274

total: Number,

275

hasMore: Boolean

276

})

277

})

278

});

279

280

// Implementation

281

const createPost = CreatePostContract.enforce(async (postData) => {

282

// Implementation details...

283

return await db.posts.create(postData);

284

});

285

286

const getPosts = GetPostsContract.enforce(async (options = {}) => {

287

// Implementation details...

288

return await db.posts.findMany(options);

289

});

290

```

291

292

### Error Handling with Contracts

293

294

```typescript

295

import { Contract, AsyncContract, String, Number, Object, Union, Literal } from "runtypes";

296

297

// Result pattern with contracts

298

const Result = <T, E>(success: Runtype<T>, error: Runtype<E>) => Union(

299

Object({ success: Literal(true), data: success }),

300

Object({ success: Literal(false), error: error })

301

);

302

303

const SafeDivisionContract = Contract({

304

receives: Tuple(Number, Number),

305

returns: Result(Number, String)

306

});

307

308

const safeDivision = SafeDivisionContract.enforce((a: number, b: number) => {

309

if (b === 0) {

310

return { success: false, error: "Division by zero" };

311

}

312

return { success: true, data: a / b };

313

});

314

315

// Usage

316

const result = safeDivision(10, 2);

317

if (result.success) {

318

console.log("Result:", result.data); // 5

319

} else {

320

console.error("Error:", result.error);

321

}

322

323

// Async error handling

324

const AsyncResultContract = AsyncContract({

325

receives: Tuple(String),

326

resolves: Result(Object({ id: Number, data: String }), String)

327

});

328

329

const fetchData = AsyncResultContract.enforce(async (url: string) => {

330

try {

331

const response = await fetch(url);

332

if (!response.ok) {

333

return { success: false, error: `HTTP ${response.status}` };

334

}

335

const data = await response.json();

336

return { success: true, data };

337

} catch (error) {

338

return { success: false, error: error.message };

339

}

340

});

341

```

342

343

### Contract Composition

344

345

```typescript

346

import { Contract, String, Number, Object } from "runtypes";

347

348

// Base contracts for reuse

349

const UserData = Object({

350

name: String,

351

email: String,

352

age: Number

353

});

354

355

const UserWithId = UserData.extend({

356

id: Number,

357

createdAt: String

358

});

359

360

// Compose contracts from base types

361

const CreateUserContract = Contract({

362

receives: Tuple(UserData),

363

returns: UserWithId

364

});

365

366

const UpdateUserContract = Contract({

367

receives: Tuple(Number, UserData.asPartial()),

368

returns: UserWithId

369

});

370

371

const DeleteUserContract = Contract({

372

receives: Tuple(Number),

373

returns: Boolean

374

});

375

376

// Generic CRUD contract factory

377

const createCrudContracts = <T>(entityType: Runtype<T>, entityWithId: Runtype<T & {id: number}>) => ({

378

create: Contract({

379

receives: Tuple(entityType),

380

returns: entityWithId

381

}),

382

383

update: Contract({

384

receives: Tuple(Number, entityType.asPartial?.() || entityType),

385

returns: entityWithId

386

}),

387

388

delete: Contract({

389

receives: Tuple(Number),

390

returns: Boolean

391

}),

392

393

getById: Contract({

394

receives: Tuple(Number),

395

returns: entityWithId.optional?.() || entityWithId

396

})

397

});

398

399

// Usage

400

const userContracts = createCrudContracts(UserData, UserWithId);

401

```

402

403

### Performance and Debugging

404

405

```typescript

406

import { Contract, String, Number } from "runtypes";

407

408

// Contract with debugging

409

const DebugContract = Contract({

410

receives: Tuple(String, Number),

411

returns: String

412

});

413

414

const debugFunction = DebugContract.enforce((name: string, count: number) => {

415

console.log(`Function called with: ${name}, ${count}`);

416

const result = `${name} called ${count} times`;

417

console.log(`Function returning: ${result}`);

418

return result;

419

});

420

421

// Conditional contracts (for development vs production)

422

const createConditionalContract = (options: ContractOptions) => {

423

if (process.env.NODE_ENV === 'development') {

424

return Contract(options);

425

}

426

// Return pass-through in production

427

return {

428

enforce: <F extends Function>(fn: F) => fn

429

};

430

};

431

432

const OptimizedContract = createConditionalContract({

433

receives: Tuple(String),

434

returns: String

435

});

436

```