x-openapi-flow 1.3.7 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,10 +11,8 @@
11
11
  [![last commit](https://img.shields.io/github/last-commit/tiago-marques/x-openapi-flow)](https://github.com/tiago-marques/x-openapi-flow/commits/main)
12
12
  ![copilot ready](https://img.shields.io/badge/Copilot-Ready-00BFA5?logo=githubcopilot&logoColor=white)
13
13
 
14
- # x-openapi-flow
15
-
16
- **OpenAPI describes APIs.**
17
- **x-openapi-flow describes how they actually work — for developers and AI.**
14
+ # OpenAPI describes APIs.
15
+ # x-openapi-flow describes how they actually work — for developers and AI.
18
16
 
19
17
  `x-openapi-flow` is an OpenAPI vendor extension and CLI for documenting and validating resource lifecycle workflows.
20
18
  It adds explicit state-machine metadata (`x-openapi-flow`) to operations and validates both schema and lifecycle graph consistency.
@@ -52,14 +50,22 @@ Default adoption path:
52
50
  2. Run `init` to create/sync sidecar flow metadata.
53
51
  3. Run `apply` whenever the OpenAPI file is regenerated.
54
52
 
55
- Full rollout guide: [docs/wiki/Adoption-Playbook.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Adoption-Playbook.md)
56
- Troubleshooting: [docs/wiki/Troubleshooting.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Troubleshooting.md)
53
+ Sidecar and output roles:
57
54
 
58
- ---
55
+ - `{context}.x.(json|yaml)`: sidecar source of lifecycle metadata (the file you edit).
56
+ - `{context}.flow.(json|yaml)`: generated OpenAPI output after merge (the file you validate and serve in docs/tools).
57
+
58
+ Practical rule:
59
59
 
60
- ## Day-to-Day Workflow (API Server Team)
60
+ 1. edit `.x`
61
+ 2. run `apply`
62
+ 3. validate/use `.flow`
63
+
64
+ Full rollout guide: [docs/wiki/getting-started/Adoption-Playbook.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/getting-started/Adoption-Playbook.md)
65
+ Troubleshooting: [docs/wiki/reference/Troubleshooting.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/reference/Troubleshooting.md)
66
+
67
+ ---
61
68
 
62
- Typical routine when your OpenAPI source changes frequently:
63
69
 
64
70
  1. Bootstrap once per project.
65
71
 
@@ -73,8 +79,6 @@ npx x-openapi-flow init
73
79
  npx x-openapi-flow apply openapi.yaml
74
80
  ```
75
81
 
76
- 3. Validate lifecycle quality before opening a PR.
77
-
78
82
  ```bash
79
83
  npx x-openapi-flow validate openapi.flow.yaml --profile strict --strict-quality
80
84
  ```
@@ -93,8 +97,6 @@ What this gives your team in practice:
93
97
  - explicit transition contracts instead of implicit tribal knowledge
94
98
  - safer API evolution with fewer integration regressions
95
99
 
96
- ---
97
-
98
100
  ## Integration Experience (What You See)
99
101
 
100
102
  ### Swagger UI
@@ -108,6 +110,8 @@ npm run apply
108
110
  npm start
109
111
  ```
110
112
 
113
+ Note: in this example, `npm run apply` prefers local sidecars (`swagger.x.yaml|yml|json`) and falls back to `examples/swagger.x.yaml|json` when a local sidecar is not present.
114
+
111
115
  Experience outcome:
112
116
 
113
117
  - operation docs enriched with `x-openapi-flow` fields
@@ -115,8 +119,8 @@ Experience outcome:
115
119
 
116
120
  Demo media:
117
121
 
118
- - GIF slot: `docs/assets/demo-swagger-ui.gif`
119
- - Image slot: `docs/assets/demo-swagger-ui.png`
122
+ ![Swagger UI Flow Lifecycle 1](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/swagger-ui-flow-lifecycle.png)
123
+ ![Swagger UI Flow Lifecycle 2](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/swagger-ui-flow-lifecycle-2.png)
120
124
 
121
125
  ### Redoc
122
126
 
@@ -136,8 +140,9 @@ Experience outcome:
136
140
 
137
141
  Demo media:
138
142
 
139
- - GIF slot: `docs/assets/demo-redoc.gif`
140
- - Image slot: `docs/assets/demo-redoc.png`
143
+ ![Redoc Flow Lifecycle 1](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/redoc-flow-lifecycle.png)
144
+ ![Redoc Flow Lifecycle 2](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/redoc-flow-lifecycle-2.png)
145
+ ![Redoc Flow Lifecycle 3](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/redoc-flow-lifecycle-3.png)
141
146
 
142
147
  ### Postman
143
148
 
@@ -157,8 +162,8 @@ Experience outcome:
157
162
 
158
163
  Demo media:
159
164
 
160
- - GIF slot: `docs/assets/demo-postman.gif`
161
- - Image slot: `docs/assets/demo-postman.png`
165
+ ![Postman Flow Lifecycle 1](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/postman-flow-lifecycle.png)
166
+ ![Postman Flow Lifecycle 2](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/postman-flow-lifecycle-2.png)
162
167
 
163
168
  ### Insomnia
164
169
 
@@ -178,15 +183,37 @@ Experience outcome:
178
183
 
179
184
  Demo media:
180
185
 
181
- - GIF slot: `docs/assets/demo-insomnia.gif`
182
- - Image slot: `docs/assets/demo-insomnia.png`
186
+ ![Insomnia Flow Lifecycle 1](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/insomnia-flow-lifecycle.png)
187
+ ![Insomnia Flow Lifecycle 2](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/insomnia-flow-lifecycle-2.png)
188
+
189
+ ### SDK (TypeScript)
190
+
191
+ Use this when you want a generated flow-aware SDK for application integration.
192
+
193
+ ```bash
194
+ cd example/sdk/typescript
195
+ npm install
196
+ npm run apply
197
+ npm run generate
198
+ npm run run:sample
199
+ ```
200
+
201
+ Experience outcome:
202
+
203
+ - generated TypeScript SDK in `sdk/` from `x-openapi-flow` metadata
204
+ - minimal runnable sample showing typed SDK usage in `src/sample.ts`
205
+ - lifecycle-oriented methods and transition-aware resource instances
206
+
207
+ Example project:
208
+
209
+ - [example/sdk/typescript/README.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/example/sdk/typescript/README.md)
183
210
 
184
211
  More integration details:
185
212
 
186
- - [docs/wiki/Swagger-UI-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Swagger-UI-Integration.md)
187
- - [docs/wiki/Redoc-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Redoc-Integration.md)
188
- - [docs/wiki/Postman-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Postman-Integration.md)
189
- - [docs/wiki/Insomnia-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Insomnia-Integration.md)
213
+ - [docs/wiki/integrations/Swagger-UI-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/integrations/Swagger-UI-Integration.md)
214
+ - [docs/wiki/integrations/Redoc-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/integrations/Redoc-Integration.md)
215
+ - [docs/wiki/integrations/Postman-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/integrations/Postman-Integration.md)
216
+ - [docs/wiki/integrations/Insomnia-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/integrations/Insomnia-Integration.md)
190
217
 
191
218
  ---
192
219
 
@@ -249,11 +276,6 @@ await payment.authorize();
249
276
  await payment.capture();
250
277
  ```
251
278
 
252
- Demo media:
253
-
254
- - GIF slot: `docs/assets/demo-sdk-generation.gif`
255
- - Image slot: `docs/assets/demo-sdk-generation.png`
256
-
257
279
  ---
258
280
 
259
281
  ## Example: Payment Flow
@@ -316,10 +338,8 @@ npx x-openapi-flow apply [openapi-file] [--flows path] [--out path]
316
338
  npx x-openapi-flow analyze [openapi-file] [--format pretty|json] [--out path] [--merge] [--flows path]
317
339
  npx x-openapi-flow generate-sdk [openapi-file] --lang typescript [--output path]
318
340
  npx x-openapi-flow export-doc-flows [openapi-file] [--output path] [--format markdown|json]
319
- npx x-openapi-flow generate-postman [openapi-file] [--output path] [--with-scripts]
320
- npx x-openapi-flow generate-insomnia [openapi-file] [--output path]
321
341
  npx x-openapi-flow generate-redoc [openapi-file] [--output path]
322
- npx x-openapi-flow graph <openapi-file> [--format mermaid|json]
342
+ <!-- Demo media removed -->
323
343
  npx x-openapi-flow doctor [--config path]
324
344
  npx x-openapi-flow completion [bash|zsh]
325
345
  ```
@@ -332,14 +352,12 @@ npx x-openapi-flow <command> --verbose
332
352
 
333
353
  Full command details:
334
354
 
335
- - [docs/wiki/CLI-Reference.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/CLI-Reference.md)
355
+ - [docs/wiki/reference/CLI-Reference.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/reference/CLI-Reference.md)
336
356
  - [x-openapi-flow/README.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/x-openapi-flow/README.md)
337
357
 
338
358
  ---
339
359
 
340
- ## Initialization Behavior
341
-
342
- Running `init`:
360
+ <!-- Demo media removed -->
343
361
 
344
362
  - Auto-detects OpenAPI source files (`openapi.yaml`, `openapi.json`, `swagger.yaml`, etc.)
345
363
  - Creates or syncs `{context}.x.(json|yaml)` (sidecar with lifecycle metadata)
@@ -357,12 +375,8 @@ npx x-openapi-flow validate openapi.yaml --profile strict
357
375
 
358
376
  ## Validation Profiles
359
377
 
360
- - `strict` (default): schema + advanced graph checks as errors; quality as warnings (or errors with `--strict-quality`)
361
- - `relaxed`: schema and orphan checks as errors; advanced/quality checks as warnings
362
378
  - `core`: schema and orphan checks only
363
-
364
- Validation covers:
365
-
379
+ <!-- Demo media removed -->
366
380
  - Schema contract correctness
367
381
  - Orphan states
368
382
  - Initial/terminal state structure
@@ -378,20 +392,11 @@ Validation covers:
378
392
  - Postman and Insomnia: generated lifecycle-aware collections/workspaces
379
393
  - SDK generator: TypeScript available, other languages planned
380
394
 
381
- Example images:
382
-
383
- ![Guided graph example](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/x-openapi-flow-overview.png)
384
- ![Swagger UI integration result](https://raw.githubusercontent.com/tiago-marques/x-openapi-flow/main/docs/assets/x-openapi-flow-extension.png)
385
-
395
+ <!-- Demo media removed -->
386
396
  Integration docs:
387
397
 
388
- - [docs/wiki/Swagger-UI-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Swagger-UI-Integration.md)
389
- - [docs/wiki/Redoc-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Redoc-Integration.md)
390
- - [docs/wiki/Postman-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Postman-Integration.md)
391
- - [docs/wiki/Insomnia-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Insomnia-Integration.md)
392
-
398
+ - [docs/wiki/integrations/Swagger-UI-Integration.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/integrations/Swagger-UI-Integration.md)
393
399
  ---
394
-
395
400
  ## Copilot Ready (AI Sidecar Authoring)
396
401
 
397
402
  Use [llm.txt](https://github.com/tiago-marques/x-openapi-flow/blob/main/llm.txt) as authoring guidance for sidecar population.
@@ -435,7 +440,7 @@ npx x-openapi-flow apply openapi.x.yaml
435
440
  - `quality-warning-api.yaml` (quality warnings)
436
441
  - `non-terminating-api.yaml` (non-terminating states)
437
442
 
438
- More examples: [docs/wiki/Real-Examples.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/Real-Examples.md)
443
+ More examples: [docs/wiki/engineering/Real-Examples.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/engineering/Real-Examples.md)
439
444
 
440
445
  ---
441
446
 
@@ -452,7 +457,7 @@ More examples: [docs/wiki/Real-Examples.md](https://github.com/tiago-marques/x-o
452
457
  ## Changelog
453
458
 
454
459
  Version history: [CHANGELOG.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/CHANGELOG.md)
455
- Release notes: [RELEASE_NOTES_v1.3.7.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/RELEASE_NOTES_v1.3.7.md)
460
+ Release notes: [docs/wiki/releases/RELEASE_NOTES_v1.4.0.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/releases/RELEASE_NOTES_v1.4.0.md)
456
461
 
457
462
  ---
458
463
 
@@ -6,14 +6,179 @@ const { loadApi } = require("../../lib/validator");
6
6
  const { buildIntermediateModel } = require("../../lib/sdk-generator");
7
7
  const { toTitleCase, pathToPostmanUrl, buildLifecycleSequences } = require("../shared/helpers");
8
8
 
9
+ function getOperationMapById(api) {
10
+ const map = new Map();
11
+ const paths = (api && api.paths) || {};
12
+
13
+ for (const [pathKey, pathItem] of Object.entries(paths)) {
14
+ for (const [method, operation] of Object.entries(pathItem || {})) {
15
+ if (!operation || typeof operation !== "object") continue;
16
+ if (!operation.operationId) continue;
17
+ map.set(operation.operationId, {
18
+ ...operation,
19
+ __path: pathKey,
20
+ __method: String(method || "get").toLowerCase(),
21
+ });
22
+ }
23
+ }
24
+
25
+ return map;
26
+ }
27
+
28
+ function buildExampleFromSchema(schema) {
29
+ if (!schema || typeof schema !== "object") return {};
30
+
31
+ if (Object.prototype.hasOwnProperty.call(schema, "example")) {
32
+ return schema.example;
33
+ }
34
+
35
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) {
36
+ return schema.enum[0];
37
+ }
38
+
39
+ if (schema.default !== undefined) {
40
+ return schema.default;
41
+ }
42
+
43
+ const type = schema.type;
44
+ if (type === "string") {
45
+ if (schema.format === "date-time") return "2026-01-01T00:00:00Z";
46
+ if (schema.format === "date") return "2026-01-01";
47
+ if (schema.format === "email") return "user@example.com";
48
+ return "string";
49
+ }
50
+ if (type === "number" || type === "integer") return 0;
51
+ if (type === "boolean") return false;
52
+ if (type === "array") {
53
+ const itemExample = buildExampleFromSchema(schema.items || {});
54
+ return itemExample === undefined ? [] : [itemExample];
55
+ }
56
+
57
+ const properties = schema.properties || {};
58
+ const required = Array.isArray(schema.required) ? schema.required : Object.keys(properties);
59
+ const payload = {};
60
+ for (const key of required) {
61
+ if (!properties[key]) continue;
62
+ payload[key] = buildExampleFromSchema(properties[key]);
63
+ }
64
+
65
+ if (Object.keys(payload).length === 0) {
66
+ for (const [key, propertySchema] of Object.entries(properties)) {
67
+ payload[key] = buildExampleFromSchema(propertySchema);
68
+ }
69
+ }
70
+
71
+ return payload;
72
+ }
73
+
74
+ function extractJsonRequestExample(rawOperation) {
75
+ if (!rawOperation || !rawOperation.requestBody || !rawOperation.requestBody.content) {
76
+ return null;
77
+ }
78
+
79
+ const jsonContent = rawOperation.requestBody.content["application/json"];
80
+ if (!jsonContent) return null;
81
+
82
+ if (jsonContent.example !== undefined) {
83
+ return jsonContent.example;
84
+ }
85
+
86
+ if (jsonContent.examples && typeof jsonContent.examples === "object") {
87
+ const firstExample = Object.values(jsonContent.examples)[0];
88
+ if (firstExample && typeof firstExample === "object" && firstExample.value !== undefined) {
89
+ return firstExample.value;
90
+ }
91
+ }
92
+
93
+ if (jsonContent.schema) {
94
+ return buildExampleFromSchema(jsonContent.schema);
95
+ }
96
+
97
+ return null;
98
+ }
99
+
100
+ function buildJourneyName(sequence, index) {
101
+ if (!Array.isArray(sequence) || sequence.length === 0) {
102
+ return `Journey ${index + 1}`;
103
+ }
104
+
105
+ if (sequence.length === 1) {
106
+ return `Journey ${index + 1}: ${sequence[0].operationId}`;
107
+ }
108
+
109
+ const first = sequence[0].operationId;
110
+ const last = sequence[sequence.length - 1].operationId;
111
+ return `Journey ${index + 1}: ${first} -> ${last}`;
112
+ }
113
+
114
+ function buildFlowDescription(operation) {
115
+ const lines = [];
116
+
117
+ if (operation.currentState) {
118
+ lines.push(`Current state: ${operation.currentState}`);
119
+ }
120
+
121
+ if (Array.isArray(operation.prerequisites) && operation.prerequisites.length > 0) {
122
+ lines.push(`Prerequisites: ${operation.prerequisites.join(", ")}`);
123
+ }
124
+
125
+ if (Array.isArray(operation.nextOperations) && operation.nextOperations.length > 0) {
126
+ const transitions = operation.nextOperations
127
+ .map((next) => {
128
+ const parts = [];
129
+ if (next.targetState) parts.push(`state ${next.targetState}`);
130
+ if (next.nextOperationId) parts.push(`op ${next.nextOperationId}`);
131
+ if (next.triggerType) parts.push(`trigger ${next.triggerType}`);
132
+ return parts.join(" | ");
133
+ })
134
+ .filter(Boolean);
135
+ if (transitions.length > 0) {
136
+ lines.push(`Next: ${transitions.join(" ; ")}`);
137
+ }
138
+ }
139
+
140
+ return lines.join("\n");
141
+ }
142
+
143
+ function createInsomniaRequest(requestId, parentId, operation, resource, rawOperation) {
144
+ const request = {
145
+ _id: requestId,
146
+ _type: "request",
147
+ parentId,
148
+ name: operation.operationId,
149
+ method: String(operation.httpMethod || "get").toUpperCase(),
150
+ url: `{{ base_url }}${pathToPostmanUrl(operation.path, resource.resourcePropertyName)}`,
151
+ headers: [],
152
+ body: {},
153
+ };
154
+
155
+ const description = buildFlowDescription(operation);
156
+ if (description) {
157
+ request.description = description;
158
+ }
159
+
160
+ if (["POST", "PUT", "PATCH"].includes(request.method)) {
161
+ request.headers.push({ name: "Content-Type", value: "application/json" });
162
+ const bodyExample = extractJsonRequestExample(rawOperation);
163
+ request.body = {
164
+ mimeType: "application/json",
165
+ text: JSON.stringify(bodyExample !== null ? bodyExample : {}, null, 2),
166
+ };
167
+ }
168
+
169
+ return request;
170
+ }
171
+
9
172
  function generateInsomniaWorkspace(options) {
10
173
  const apiPath = path.resolve(options.apiPath);
11
174
  const outputPath = path.resolve(options.outputPath || path.join(process.cwd(), "x-openapi-flow.insomnia.json"));
12
175
 
13
176
  const api = loadApi(apiPath);
14
177
  const model = buildIntermediateModel(api);
178
+ const operationMapById = getOperationMapById(api);
15
179
 
16
180
  const workspaceId = "wrk_x_openapi_flow";
181
+ const environmentId = "env_x_openapi_flow_base";
17
182
  const resources = [
18
183
  {
19
184
  _id: workspaceId,
@@ -22,6 +187,15 @@ function generateInsomniaWorkspace(options) {
22
187
  description: `Generated from ${apiPath}`,
23
188
  scope: "collection",
24
189
  },
190
+ {
191
+ _id: environmentId,
192
+ _type: "environment",
193
+ parentId: workspaceId,
194
+ name: "Base Environment",
195
+ data: {
196
+ base_url: "http://localhost:3000",
197
+ },
198
+ },
25
199
  ];
26
200
 
27
201
  for (const resource of model.resources) {
@@ -34,22 +208,44 @@ function generateInsomniaWorkspace(options) {
34
208
  });
35
209
 
36
210
  const sequences = buildLifecycleSequences(resource);
37
- const operations = sequences.length > 0
38
- ? Array.from(new Map(sequences.flat().map((op) => [op.operationId, op])).values())
39
- : resource.operations.filter((operation) => operation.hasFlow);
40
-
41
- operations.forEach((operation, index) => {
42
- const requestId = `req_${resource.resourcePropertyName}_${index + 1}`;
43
- resources.push({
44
- _id: requestId,
45
- _type: "request",
46
- parentId: groupId,
47
- name: operation.operationId,
48
- method: String(operation.httpMethod || "get").toUpperCase(),
49
- url: `{{ base_url }}${pathToPostmanUrl(operation.path, resource.resourcePropertyName)}`,
50
- body: {},
211
+ if (sequences.length > 0) {
212
+ sequences.forEach((sequence, sequenceIndex) => {
213
+ const journeyId = `fld_${resource.resourcePropertyName}_journey_${sequenceIndex + 1}`;
214
+ resources.push({
215
+ _id: journeyId,
216
+ _type: "request_group",
217
+ parentId: groupId,
218
+ name: buildJourneyName(sequence, sequenceIndex),
219
+ });
220
+
221
+ sequence.forEach((operation, operationIndex) => {
222
+ const requestId = `req_${resource.resourcePropertyName}_${sequenceIndex + 1}_${operationIndex + 1}`;
223
+ resources.push(
224
+ createInsomniaRequest(
225
+ requestId,
226
+ journeyId,
227
+ operation,
228
+ resource,
229
+ operationMapById.get(operation.operationId)
230
+ )
231
+ );
232
+ });
51
233
  });
52
- });
234
+ } else {
235
+ const operations = resource.operations.filter((operation) => operation.hasFlow);
236
+ operations.forEach((operation, index) => {
237
+ const requestId = `req_${resource.resourcePropertyName}_${index + 1}`;
238
+ resources.push(
239
+ createInsomniaRequest(
240
+ requestId,
241
+ groupId,
242
+ operation,
243
+ resource,
244
+ operationMapById.get(operation.operationId)
245
+ )
246
+ );
247
+ });
248
+ }
53
249
  }
54
250
 
55
251
  const exportPayload = {