or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

analysis-metrics.mdapi-definition-reduction.mdextensions-customization.mdindex.mdopenapi-definition-management.mdoperation-discovery-analysis.mdparameter-handling-json-schema.mdrequest-response-management.mdschema-dereferencing-references.mdsecurity-authentication.mdserver-url-management.mdutils.md

api-definition-reduction.mddocs/

0

# API Definition Reduction

1

2

Tools for extracting subsets of large OpenAPI definitions by tags or specific paths, creating focused API definitions.

3

4

## Capabilities

5

6

### Reduce API Definition

7

8

Extract a subset of an OpenAPI definition based on tags or specific paths and methods.

9

10

```typescript { .api }

11

/**

12

* Reduce an OpenAPI definition to specific tags or paths

13

* @param definition - OpenAPI definition to reduce

14

* @param opts - Reduction options specifying what to keep

15

* @returns New OpenAPI definition containing only specified elements

16

* @throws Error if all paths are removed or invalid options provided

17

*/

18

function reducer(definition: OASDocument, opts?: ReducerOptions): OASDocument;

19

20

interface ReducerOptions {

21

/** Object mapping paths to arrays of methods (or '*' for all methods) */

22

paths?: Record<string, string[] | '*'>;

23

/** Array of tag names to include */

24

tags?: string[];

25

}

26

```

27

28

**Usage Examples:**

29

30

```typescript

31

import reducer from "oas/reducer";

32

33

const originalDef = {

34

openapi: "3.1.0",

35

info: { title: "Full API", version: "1.0.0" },

36

paths: {

37

"/users": { get: { tags: ["users"] }, post: { tags: ["users"] } },

38

"/orders": { get: { tags: ["orders"] }, post: { tags: ["orders"] } },

39

"/admin": { get: { tags: ["admin"] }, delete: { tags: ["admin"] } }

40

}

41

};

42

43

// Reduce by tags

44

const userAPI = reducer(originalDef, {

45

tags: ['users']

46

});

47

// Result: Only /users paths included

48

49

// Reduce by specific paths and methods

50

const readOnlyAPI = reducer(originalDef, {

51

paths: {

52

'/users': ['get'],

53

'/orders': ['get']

54

}

55

});

56

// Result: Only GET methods for /users and /orders

57

58

// Reduce by paths with all methods

59

const userAndOrderAPI = reducer(originalDef, {

60

paths: {

61

'/users': '*',

62

'/orders': '*'

63

}

64

});

65

// Result: All methods for /users and /orders paths

66

```

67

68

## Reduction Strategies

69

70

### Tag-Based Reduction

71

72

Extract operations based on OpenAPI tags for logical grouping:

73

74

```typescript

75

// Create API subsets by functional area

76

const userManagementAPI = reducer(fullAPI, {

77

tags: ['users', 'profiles', 'authentication']

78

});

79

80

const orderProcessingAPI = reducer(fullAPI, {

81

tags: ['orders', 'payments', 'shipping']

82

});

83

84

const adminAPI = reducer(fullAPI, {

85

tags: ['admin', 'monitoring', 'configuration']

86

});

87

88

console.log("Created 3 focused API definitions from main API");

89

```

90

91

### Path-Specific Reduction

92

93

Extract specific endpoints and methods:

94

95

```typescript

96

// Create read-only API

97

const readOnlyAPI = reducer(fullAPI, {

98

paths: {

99

'/users': ['get'],

100

'/users/{id}': ['get'],

101

'/orders': ['get'],

102

'/orders/{id}': ['get'],

103

'/products': ['get'],

104

'/products/{id}': ['get']

105

}

106

});

107

108

// Create write-only API for integrations

109

const writeOnlyAPI = reducer(fullAPI, {

110

paths: {

111

'/webhooks': '*',

112

'/users': ['post', 'put'],

113

'/orders': ['post', 'put', 'patch']

114

}

115

});

116

117

// Extract specific workflow

118

const checkoutAPI = reducer(fullAPI, {

119

paths: {

120

'/products': ['get'],

121

'/cart': '*',

122

'/checkout': '*',

123

'/payments': ['post']

124

}

125

});

126

```

127

128

### Combined Reduction

129

130

Use both tags and paths for fine-grained control:

131

132

```typescript

133

// First reduce by tags, then by paths

134

const baseAPI = reducer(fullAPI, {

135

tags: ['public', 'users']

136

});

137

138

const publicReadAPI = reducer(baseAPI, {

139

paths: {

140

'/users': ['get'],

141

'/users/{id}': ['get'],

142

'/health': ['get'],

143

'/status': ['get']

144

}

145

});

146

```

147

148

## Component and Reference Management

149

150

### Automatic Component Cleanup

151

152

The reducer automatically removes unused components:

153

154

```typescript

155

const originalComponents = fullAPI.components?.schemas || {};

156

console.log(`Original schemas: ${Object.keys(originalComponents).length}`);

157

158

const reducedAPI = reducer(fullAPI, { tags: ['users'] });

159

const reducedComponents = reducedAPI.components?.schemas || {};

160

console.log(`Reduced schemas: ${Object.keys(reducedComponents).length}`);

161

162

// Only components referenced by included operations are kept

163

```

164

165

### Reference Preservation

166

167

All `$ref` pointers used by included operations are preserved:

168

169

```typescript

170

// If User schema references Address schema, both are kept

171

const userAPI = reducer(fullAPI, {

172

paths: { '/users/{id}': ['get'] }

173

});

174

175

// Both User and Address schemas preserved if User references Address

176

const schemas = userAPI.components?.schemas || {};

177

console.log("Preserved schemas:", Object.keys(schemas));

178

```

179

180

### Deep Reference Resolution

181

182

The reducer follows reference chains to preserve all dependencies:

183

184

```typescript

185

// Schema dependency chain: User -> Profile -> Address -> Country

186

// Reducing to just /users endpoint preserves entire chain

187

const minimalAPI = reducer(fullAPI, {

188

paths: { '/users/{id}': ['get'] }

189

});

190

191

// All schemas in the dependency chain are preserved

192

const preservedSchemas = Object.keys(minimalAPI.components?.schemas || {});

193

console.log("Dependency chain preserved:", preservedSchemas);

194

```

195

196

## Advanced Reduction Patterns

197

198

### Multi-Version API Creation

199

200

```typescript

201

// Create different API versions from single definition

202

const v1API = reducer(fullAPI, {

203

tags: ['v1-users', 'v1-orders']

204

});

205

206

const v2API = reducer(fullAPI, {

207

tags: ['v2-users', 'v2-orders', 'v2-products']

208

});

209

210

// Save as separate OpenAPI files

211

writeFileSync('api-v1.json', JSON.stringify(v1API, null, 2));

212

writeFileSync('api-v2.json', JSON.stringify(v2API, null, 2));

213

```

214

215

### Client SDK Generation

216

217

```typescript

218

// Create focused APIs for different client types

219

const mobileAPI = reducer(fullAPI, {

220

tags: ['mobile', 'auth', 'core']

221

});

222

223

const webAPI = reducer(fullAPI, {

224

tags: ['web', 'auth', 'admin', 'reporting']

225

});

226

227

const partnerAPI = reducer(fullAPI, {

228

tags: ['partner', 'webhooks', 'sync']

229

});

230

231

// Generate SDKs from focused definitions

232

generateSDK('mobile', mobileAPI);

233

generateSDK('web', webAPI);

234

generateSDK('partner', partnerAPI);

235

```

236

237

### Documentation Splitting

238

239

```typescript

240

// Create focused documentation sets

241

const userDocs = reducer(fullAPI, {

242

tags: ['authentication', 'users', 'profiles']

243

});

244

245

const developerDocs = reducer(fullAPI, {

246

tags: ['webhooks', 'api-keys', 'rate-limits']

247

});

248

249

const adminDocs = reducer(fullAPI, {

250

tags: ['admin', 'monitoring', 'configuration']

251

});

252

```

253

254

### Testing Subset Creation

255

256

```typescript

257

// Create test-specific API subsets

258

const smokeTestAPI = reducer(fullAPI, {

259

paths: {

260

'/health': ['get'],

261

'/users': ['get', 'post'],

262

'/orders': ['get']

263

}

264

});

265

266

const integrationTestAPI = reducer(fullAPI, {

267

tags: ['integration', 'webhooks', 'callbacks']

268

});

269

270

const loadTestAPI = reducer(fullAPI, {

271

paths: {

272

'/users': ['get'],

273

'/products': ['get'],

274

'/search': ['get', 'post']

275

}

276

});

277

```

278

279

## Error Handling and Validation

280

281

### Input Validation

282

283

```typescript

284

try {

285

// Empty reduction options - returns original

286

const unchanged = reducer(fullAPI, {});

287

288

// Case-insensitive matching

289

const mixedCase = reducer(fullAPI, {

290

tags: ['Users', 'ORDERS'] // Matches 'users' and 'orders'

291

});

292

293

// Invalid path removal

294

const filtered = reducer(fullAPI, {

295

paths: {

296

'/nonexistent': ['get'], // Ignored if path doesn't exist

297

'/users': ['get'] // Kept if path exists

298

}

299

});

300

301

} catch (error) {

302

console.error("Reduction failed:", error.message);

303

}

304

```

305

306

### Complete Path Removal

307

308

```typescript

309

try {

310

// This will throw an error - can't remove all paths

311

const emptyAPI = reducer(fullAPI, {

312

tags: ['nonexistent-tag']

313

});

314

} catch (error) {

315

console.error(error.message);

316

// "All paths in the API definition were removed. Did you supply the right path name to reduce by?"

317

}

318

```

319

320

### Validation Requirements

321

322

```typescript

323

// Validate reduction before processing

324

function validateReduction(definition: OASDocument, opts: ReducerOptions): boolean {

325

const availableTags = new Set();

326

const availablePaths = new Set(Object.keys(definition.paths || {}));

327

328

// Collect all available tags

329

Object.values(definition.paths || {}).forEach(pathItem => {

330

Object.values(pathItem).forEach(operation => {

331

if ('tags' in operation && Array.isArray(operation.tags)) {

332

operation.tags.forEach(tag => availableTags.add(tag.toLowerCase()));

333

}

334

});

335

});

336

337

// Validate tag options

338

if (opts.tags) {

339

const invalidTags = opts.tags.filter(tag => !availableTags.has(tag.toLowerCase()));

340

if (invalidTags.length > 0) {

341

console.warn(`Tags not found: ${invalidTags.join(', ')}`);

342

}

343

}

344

345

// Validate path options

346

if (opts.paths) {

347

const invalidPaths = Object.keys(opts.paths).filter(path =>

348

!availablePaths.has(path.toLowerCase())

349

);

350

if (invalidPaths.length > 0) {

351

console.warn(`Paths not found: ${invalidPaths.join(', ')}`);

352

}

353

}

354

355

return true;

356

}

357

358

// Use validation before reduction

359

if (validateReduction(fullAPI, reductionOptions)) {

360

const reducedAPI = reducer(fullAPI, reductionOptions);

361

}

362

```

363

364

## Integration Examples

365

366

### Build Pipeline Integration

367

368

```typescript

369

// Generate multiple API artifacts in build process

370

async function buildAPIArtifacts(sourceAPI: OASDocument) {

371

const artifacts = [

372

{ name: 'public', opts: { tags: ['public'] } },

373

{ name: 'admin', opts: { tags: ['admin'] } },

374

{ name: 'mobile', opts: { tags: ['mobile', 'core'] } },

375

{ name: 'partner', opts: { tags: ['partner', 'webhooks'] } }

376

];

377

378

artifacts.forEach(({ name, opts }) => {

379

try {

380

const reducedAPI = reducer(sourceAPI, opts);

381

const filename = `dist/api-${name}.json`;

382

383

writeFileSync(filename, JSON.stringify(reducedAPI, null, 2));

384

console.log(`✅ Generated ${filename}`);

385

386

// Validate generated API

387

const opCount = Object.keys(reducedAPI.paths || {}).length;

388

console.log(` ${opCount} paths included`);

389

390

} catch (error) {

391

console.error(`❌ Failed to generate ${name}: ${error.message}`);

392

}

393

});

394

}

395

```

396

397

### Dynamic API Serving

398

399

```typescript

400

// Serve different API subsets based on user permissions

401

function getAPIForUser(user: any, fullAPI: OASDocument): OASDocument {

402

if (user.role === 'admin') {

403

return fullAPI; // Full access

404

}

405

406

if (user.role === 'partner') {

407

return reducer(fullAPI, {

408

tags: ['partner', 'webhooks', 'public']

409

});

410

}

411

412

if (user.role === 'developer') {

413

return reducer(fullAPI, {

414

tags: ['public', 'auth', 'core']

415

});

416

}

417

418

// Default: public endpoints only

419

return reducer(fullAPI, {

420

tags: ['public']

421

});

422

}

423

```