docs
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
```