@powerduck/openapi-request 0.2.2 → 0.2.3
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 +316 -600
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,681 +1,390 @@
|
|
|
1
1
|
# @powerduck/openapi-request
|
|
2
2
|
|
|
3
|
-
OpenAPI
|
|
4
|
-
One client surface, one event model, one write-back pipeline.
|
|
3
|
+
Execute OpenAPI operations with full parameter serialization, security resolution, and response parsing. Supports fetch, axios, and custom HTTP clients. Built for browsers, Node.js, and Edge Functions.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@powerduck/openapi-request)
|
|
6
|
+
[](https://github.com/PowerDuckie/openapi-request/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
## Links
|
|
9
|
+
|
|
10
|
+
- [Official Website](https://www.powerduck.com/opensource/openapi-request.html)
|
|
11
|
+
- [Documentation](https://www.powerduck.com/docs/openapi-request/introduction)
|
|
12
|
+
- [Live Demo](https://www.powerduck.com/demo/openapi-request.html)
|
|
13
|
+
- [GitHub](https://github.com/PowerDuckie/openapi-request)
|
|
14
|
+
- [npm](https://www.npmjs.com/package/@powerduck/openapi-request)
|
|
15
|
+
|
|
16
|
+
---
|
|
7
17
|
|
|
8
|
-
|
|
9
|
-
- **Incremental SSE / ndjson streaming** with `onEvent` callbacks and `onResponseStart` early classification.
|
|
10
|
-
- **Manual sessions** for WebSocket, gRPC (all four RPC modes) and MCP (Streamable HTTP + stdio).
|
|
11
|
-
- **gRPC** via reflection or `.proto` files, with metadata / status / trailers surfaced as events.
|
|
12
|
-
- **MCP** tools, prompts, resources and resource templates over HTTP or stdio.
|
|
13
|
-
- **GraphQL** introspection, query/mutation generation and subscription over WebSocket.
|
|
14
|
-
- **Postman-runtime** under the hood for HTTP, with full script (pre-request / test) support.
|
|
15
|
-
- **Response write-back** — merges live responses into the OpenAPI document as `responses` objects.
|
|
18
|
+
## Quick Start
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
### Install
|
|
18
21
|
|
|
19
22
|
```bash
|
|
20
23
|
npm install @powerduck/openapi-request
|
|
21
24
|
```
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## Quick Start
|
|
26
|
+
### Create a client and send a request
|
|
26
27
|
|
|
27
|
-
```
|
|
28
|
+
```typescript
|
|
28
29
|
import { createClient } from "@powerduck/openapi-request";
|
|
29
30
|
|
|
30
31
|
const client = createClient();
|
|
31
32
|
|
|
32
|
-
// 1. Decide how to render before sending anything.
|
|
33
|
-
const prepared = client.prepare({
|
|
34
|
-
spec: openApiDoc,
|
|
35
|
-
target: { path: "/users/{id}", method: "get" },
|
|
36
|
-
});
|
|
37
|
-
console.log(prepared.display.mode); // "response" | "event-list" | "duplex-session"
|
|
38
|
-
console.log(prepared.stream.kind); // "none" | "sse" | "grpc-unary" | ...
|
|
39
|
-
|
|
40
|
-
// 2. Send a one-shot request (HTTP / GraphQL / MCP one-shot).
|
|
41
33
|
const result = await client.send({
|
|
42
|
-
spec:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
console.log("streaming:", info.streaming); // true for SSE, switch UI early
|
|
48
|
-
},
|
|
49
|
-
onEvent: (event) => {
|
|
50
|
-
console.log(event.event, event.data); // incremental SSE events
|
|
34
|
+
spec: openApiDocument,
|
|
35
|
+
operationId: "getUserById",
|
|
36
|
+
parameters: {
|
|
37
|
+
path: { id: "123" },
|
|
38
|
+
query: { include: "profile" },
|
|
51
39
|
},
|
|
52
40
|
});
|
|
53
41
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
session.onEvent((e) => console.log(e));
|
|
58
|
-
await session.send({ type: "ping" });
|
|
59
|
-
await session.close();
|
|
42
|
+
console.log(result.response.status); // e.g. 200
|
|
43
|
+
console.log(result.response.body); // parsed response body
|
|
44
|
+
console.log(result.response.headers); // response headers
|
|
60
45
|
```
|
|
61
46
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
## API Reference
|
|
47
|
+
### Prepare and send separately
|
|
65
48
|
|
|
66
|
-
|
|
49
|
+
```typescript
|
|
50
|
+
import { createClient } from "@powerduck/openapi-request";
|
|
67
51
|
|
|
68
|
-
|
|
52
|
+
const client = createClient();
|
|
69
53
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
54
|
+
// Prepare builds the request without sending
|
|
55
|
+
const prepared = client.prepare({
|
|
56
|
+
spec: openApiDocument,
|
|
57
|
+
operationId: "listUsers",
|
|
58
|
+
parameters: {
|
|
59
|
+
query: { page: 1, limit: 20 },
|
|
60
|
+
},
|
|
61
|
+
});
|
|
74
62
|
|
|
75
|
-
|
|
63
|
+
console.log(prepared.url); // final URL with query string
|
|
64
|
+
console.log(prepared.method); // HTTP method
|
|
65
|
+
console.log(prepared.headers); // resolved headers
|
|
76
66
|
|
|
77
|
-
|
|
67
|
+
// Send the prepared request
|
|
68
|
+
const result = await client.send(prepared);
|
|
69
|
+
```
|
|
78
70
|
|
|
79
|
-
|
|
80
|
-
|-------|------|-------------|
|
|
81
|
-
| `protocol` | `string` | `"http"` \| `"sse"` \| `"websocket"` \| `"grpc"` \| `"graphql"` \| `"mcp"` |
|
|
82
|
-
| `transport` | `string` | `"http"` \| `"websocket"` \| `"grpc"` \| `"stdio"` |
|
|
83
|
-
| `display.mode` | `"response"` \| `"event-list"` \| `"duplex-session"` | How to render the result. |
|
|
84
|
-
| `stream.kind` | `StreamKind` | Fine-grained streaming taxonomy. |
|
|
85
|
-
| `stream.expected` | `boolean` | True when the operation declares or implies streaming. |
|
|
86
|
-
| `openapi.extensions` | `Record<string, unknown>` | The seven `x-*` extensions derived from the operation. |
|
|
87
|
-
| `warnings` | `string[]` | Non-fatal issues found during preparation. |
|
|
71
|
+
### With security values
|
|
88
72
|
|
|
89
|
-
|
|
73
|
+
```typescript
|
|
74
|
+
import { createClient } from "@powerduck/openapi-request";
|
|
90
75
|
|
|
91
|
-
|
|
76
|
+
const client = createClient({
|
|
77
|
+
securityValues: {
|
|
78
|
+
bearerAuth: "YOUR_ACCESS_TOKEN",
|
|
79
|
+
apiKey: "YOUR_API_KEY",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
92
82
|
|
|
93
|
-
|
|
83
|
+
const result = await client.send({
|
|
84
|
+
spec: openApiDocument,
|
|
85
|
+
operationId: "createUser",
|
|
86
|
+
parameters: {
|
|
87
|
+
body: { name: "Ada", email: "ada@example.com" },
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|
|
94
91
|
|
|
95
|
-
|
|
92
|
+
### Custom fetch function
|
|
96
93
|
|
|
97
|
-
|
|
94
|
+
```typescript
|
|
95
|
+
import { createClient } from "@powerduck/openapi-request";
|
|
98
96
|
|
|
99
|
-
|
|
97
|
+
const client = createClient({
|
|
98
|
+
fetchFn: async (url, init) => {
|
|
99
|
+
console.log("Request:", init.method, url);
|
|
100
|
+
const response = await fetch(url, init);
|
|
101
|
+
console.log("Response:", response.status);
|
|
102
|
+
return response;
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
```
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|----------|---------|
|
|
103
|
-
| `mcp` | `{ protocol: "mcp", endpoint, headers?, transport?, command?, args?, cwd? }` |
|
|
104
|
-
| `grpc` | `{ protocol: "grpc", address, reflection?, protoPaths?, includeDirs?, metadata?, channelOptions? }` |
|
|
107
|
+
---
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
## Features
|
|
107
110
|
|
|
108
|
-
|
|
111
|
+
- **Full OpenAPI parameter serialization** — path, query, header, cookie parameters with all `style` and `explode` combinations (form, spaceDelimited, pipeDelimited, label, matrix, simple, deepObject)
|
|
112
|
+
- **Security resolution** — Bearer, Basic, API key (header/query/cookie), with per-request or client-level `securityValues`
|
|
113
|
+
- **Request body handling** — JSON, form-urlencoded, multipart/form-data (text + file fields), XML, text, binary
|
|
114
|
+
- **Response parsing** — automatic JSON parsing, text fallback, content-type detection
|
|
115
|
+
- **Two-phase execution** — `prepare()` builds the request without sending, `send()` executes it
|
|
116
|
+
- **Custom HTTP client** — plug in fetch, axios, node-fetch, or any compatible function
|
|
117
|
+
- **Server URL selection** — override or select from spec `servers` array
|
|
118
|
+
- **Timeout and abort** — per-request timeout with `AbortController` integration
|
|
119
|
+
- **Postman script support** — run `pm.test()` / `pm.expect()` scripts from `x-postman-scripts`
|
|
120
|
+
- **Batch requests** — send multiple operations with configurable concurrency
|
|
121
|
+
- **Browser and Node.js compatible** — works in browsers, Edge Functions, and Node.js
|
|
122
|
+
- **Dual ESM/CJS builds** — works with `import` and `require`, with bundled TypeScript declarations
|
|
109
123
|
|
|
110
124
|
---
|
|
111
125
|
|
|
112
|
-
##
|
|
113
|
-
|
|
114
|
-
The full option shape passed to `client.send()`. Protocol-specific fields are nested under `runner`, `websocket`, `graphql`, `mcp`, `grpc`.
|
|
115
|
-
|
|
116
|
-
| Field | Type | Default | Description |
|
|
117
|
-
|-------|------|---------|-------------|
|
|
118
|
-
| `spec` | `OpenApiDocument` | **required** | The OpenAPI 3.2 document. |
|
|
119
|
-
| `target` | `OperationTarget` | **required** | `{ path?, method?, operationId? }` — identifies the operation. |
|
|
120
|
-
| `values` | `RequestValues` | `{}` | Path/query/header/cookie/body values to inject. |
|
|
121
|
-
| `serverUrl` | `string` | `spec.servers[0].url` | Overrides the server URL. |
|
|
122
|
-
| `serverVariables` | `Record<string,string>` | `{}` | Variables for `{var}` placeholders in the server URL. |
|
|
123
|
-
| `variables` | `Record<string,string>` | `{}` | Postman environment variables (`{{name}}`). |
|
|
124
|
-
| `globals` | `Record<string,string>` | `{}` | Postman global variables. |
|
|
125
|
-
| `localVariables` | `Record<string,string>` | `{}` | Postman local variables. |
|
|
126
|
-
| `auth` | `AuthConfig` | — | `{ type: "bearer"\|"basic"\|"apikey"\|"none", ... }`. |
|
|
127
|
-
| `scripts` | `ScriptConfig` | `{}` | Pre-request / test scripts and `x-postman-scripts` handling. |
|
|
128
|
-
| `runner` | `RuntimeRunOptions` | `{}` | Full postman-runtime passthrough. Highest precedence. |
|
|
129
|
-
| `websocket` | `WebSocketOptions` | `{}` | WebSocket-specific options. |
|
|
130
|
-
| `graphql` | `GraphQLOptions` | `{}` | GraphQL-specific options. |
|
|
131
|
-
| `mcp` | `McpOptions` | `{}` | MCP-specific options. |
|
|
132
|
-
| `grpc` | `any` | `{}` | gRPC-specific options (used by the gRPC adapter). |
|
|
133
|
-
| `timeout` | `number` | `30000` | Convenience shortcut for `runner.timeout.request`. `0` = unlimited. |
|
|
134
|
-
| `maxEvents` | `number` | `100` | Maximum streaming events to retain. |
|
|
135
|
-
| `maxStreamMs` | `number` | `30000` | Maximum streaming duration before sampling stops. |
|
|
136
|
-
| `maxResponseSize` | `number` | — | Hard byte ceiling for the response body. Rejected if `<= 0`. |
|
|
137
|
-
| `writeBack` | `boolean` | `true` | Set `false` to skip OpenAPI write-back. |
|
|
138
|
-
| `signal` | `AbortSignal` | — | Cancels the run; returns a partial result. |
|
|
139
|
-
| `onEvent` | `(event: StreamEvent) => void` | — | Incremental SSE / ndjson events. Called defensively (errors swallowed). |
|
|
140
|
-
| `onResponseStart` | `(info: ResponseStartInfo) => void` | — | Fires when headers arrive. `info.streaming` is the early SSE flag. |
|
|
141
|
-
| `onConsole` | `(log: ConsoleLog) => void` | — | Script `console.*` output. |
|
|
142
|
-
| `onAssertion` | `(a: AssertionResult) => void` | — | Individual test assertion results. |
|
|
143
|
-
| `onOpen` | `(info: {url, protocol?, headers}) => void` | — | WebSocket connection established. |
|
|
144
|
-
|
|
145
|
-
### StreamParserOptions (also on SendOptions)
|
|
146
|
-
|
|
147
|
-
| Field | Type | Default | Description |
|
|
148
|
-
|-------|------|---------|-------------|
|
|
149
|
-
| `maxBufferChars` | `number` | `4194304` (4 Mi) | Max chars buffered while waiting for an event boundary. |
|
|
150
|
-
| `maxEventChars` | `number` | `1048576` (1 Mi) | Max chars in a single event's `data`. |
|
|
151
|
-
| `inheritEventId` | `boolean` | `true` | Attach the last seen `id` to events that omit one. Set `false` for spec-faithful streams. |
|
|
126
|
+
## API
|
|
152
127
|
|
|
153
|
-
|
|
128
|
+
### `createClient(options?: CreateClientOptions): OpenApiClient`
|
|
154
129
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"info": { "title": "example", "version": "1.0.0" },
|
|
165
|
-
"servers": [{ "url": "https://api.example.com" }],
|
|
166
|
-
"paths": {
|
|
167
|
-
"/users/{id}": {
|
|
168
|
-
"get": {
|
|
169
|
-
"operationId": "getUser",
|
|
170
|
-
"parameters": [
|
|
171
|
-
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
|
|
172
|
-
],
|
|
173
|
-
"responses": {
|
|
174
|
-
"200": {
|
|
175
|
-
"description": "ok",
|
|
176
|
-
"content": { "application/json": {} }
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
"/events": {
|
|
182
|
-
"get": {
|
|
183
|
-
"operationId": "streamEvents",
|
|
184
|
-
"responses": {
|
|
185
|
-
"200": {
|
|
186
|
-
"description": "ok",
|
|
187
|
-
"content": { "text/event-stream": {} }
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
130
|
+
Creates a new client instance.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
interface CreateClientOptions {
|
|
134
|
+
fetchFn?: (url: string, init: RequestInit) => Promise<Response>;
|
|
135
|
+
securityValues?: Record<string, string>;
|
|
136
|
+
serverUrl?: string;
|
|
137
|
+
timeoutMs?: number;
|
|
138
|
+
validateResponse?: boolean;
|
|
193
139
|
}
|
|
194
140
|
```
|
|
195
141
|
|
|
196
|
-
### `
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
| `maxInvokableNestedRequests` | `number` | `5` | Max nested requests from scripts (`pm.sendRequest`). |
|
|
225
|
-
| `sslKeyLogFile` | `string` | — | Path to write TLS session keys (for Wireshark debugging). |
|
|
226
|
-
|
|
227
|
-
### `runner` top-level options
|
|
228
|
-
|
|
229
|
-
| Field | Type | Default | Description |
|
|
230
|
-
|-------|------|---------|-------------|
|
|
231
|
-
| `timeout.request` | `number` | `30000` | Per-request timeout in ms. `0` = unlimited. |
|
|
232
|
-
| `timeout.script` | `number` | `15000` | Script execution timeout in ms. |
|
|
233
|
-
| `timeout.global` | `number` | computed | Global run timeout. Derived from `request + streamBudget + 15000` slack. |
|
|
234
|
-
| `iterationCount` | `number` | `1` | Number of iterations. Must be >= 1. |
|
|
235
|
-
| `data` | `Array<Record<string,unknown>>` | — | Data-driven iteration values. When provided, `iterationCount` defaults to `data.length`. |
|
|
236
|
-
| `stopOnError` | `boolean` | `false` | Halt the run on a request error. |
|
|
237
|
-
| `abortOnError` | `boolean` | `false` | Abort immediately on error (no cleanup callbacks). |
|
|
238
|
-
| `stopOnFailure` | `boolean` | `false` | Halt on a failed test assertion. |
|
|
239
|
-
| `abortOnFailure` | `boolean` | `false` | Abort immediately on assertion failure. |
|
|
240
|
-
| `environment` | `any` | built from `variables` | Postman environment. Override to supply a custom VariableScope. |
|
|
241
|
-
| `globals` | `any` | built from `globals` | Postman globals. |
|
|
242
|
-
| `localVariables` | `any` | built from `localVariables` | Postman local variables. |
|
|
243
|
-
| `proxies` | `any` | — | Postman proxy configuration list. |
|
|
244
|
-
| `systemProxy` | `(url, cb) => void` | — | Callback to resolve the system proxy for a URL. |
|
|
245
|
-
| `ignoreProxyEnvironmentVariables` | `boolean` | `false` | Ignore `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` env vars. |
|
|
246
|
-
| `certificates` | `any` | — | Client certificate list. |
|
|
247
|
-
| `delay.item` | `number` | `0` | Delay between requests in ms. |
|
|
248
|
-
| `delay.iteration` | `number` | `0` | Delay between iterations in ms. |
|
|
249
|
-
| `entrypoint.execute` | `string` | — | Name of the item to start from. |
|
|
250
|
-
| `entrypoint.lookupStrategy` | `"idOrName" \| "path"` | — | How to resolve the entrypoint. |
|
|
251
|
-
| `secretResolver` | `(ctx, cb) => void` | — | Resolve secrets referenced in the collection. |
|
|
252
|
-
| `fileResolver` | `unknown` | — | Resolve file references in request bodies. |
|
|
253
|
-
|
|
254
|
-
### SSE streaming example
|
|
255
|
-
|
|
256
|
-
```ts
|
|
257
|
-
const result = await client.send({
|
|
258
|
-
spec,
|
|
259
|
-
target: { path: "/events", method: "get" },
|
|
260
|
-
maxStreamMs: 60000,
|
|
261
|
-
maxEvents: 100,
|
|
262
|
-
onResponseStart: (info) => {
|
|
263
|
-
if (info.streaming) {
|
|
264
|
-
// Switch the UI to the event-list view immediately — do not wait
|
|
265
|
-
// for send() to resolve.
|
|
266
|
-
renderEventList();
|
|
267
|
-
}
|
|
268
|
-
},
|
|
269
|
-
onEvent: (event) => {
|
|
270
|
-
appendEvent(event); // called incrementally, ~every chunk
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
// result.response.events contains all retained events.
|
|
275
|
-
// result.response.truncated / stopReason explain why sampling ended.
|
|
142
|
+
### `client.send(options: SendOptions): Promise<SendResult>`
|
|
143
|
+
|
|
144
|
+
Prepares and sends a request in one call.
|
|
145
|
+
|
|
146
|
+
### `client.prepare(options: SendOptions): PreparedRequest`
|
|
147
|
+
|
|
148
|
+
Builds the request without sending. Returns a `PreparedRequest` that can be inspected or passed to `send()`.
|
|
149
|
+
|
|
150
|
+
### `SendOptions`
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
interface SendOptions {
|
|
154
|
+
spec: OpenApiDocument; // Parsed OpenAPI 3.2 document
|
|
155
|
+
operationId?: string; // Operation ID (alternative to path + method)
|
|
156
|
+
path?: string; // Path template, e.g. "/users/{id}"
|
|
157
|
+
method?: string; // HTTP method, e.g. "get"
|
|
158
|
+
parameters?: {
|
|
159
|
+
path?: Record<string, unknown>;
|
|
160
|
+
query?: Record<string, unknown>;
|
|
161
|
+
header?: Record<string, string>;
|
|
162
|
+
cookie?: Record<string, string>;
|
|
163
|
+
body?: unknown;
|
|
164
|
+
};
|
|
165
|
+
securityValues?: Record<string, string>;
|
|
166
|
+
serverUrl?: string;
|
|
167
|
+
timeoutMs?: number;
|
|
168
|
+
signal?: AbortSignal;
|
|
169
|
+
}
|
|
276
170
|
```
|
|
277
171
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
"responses": { "200": { "description": "duplex" } }
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
172
|
+
> Either `operationId` or `path` + `method` must be provided to identify the operation.
|
|
173
|
+
|
|
174
|
+
### `SendResult`
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
interface SendResult extends ExecResult {
|
|
178
|
+
response: {
|
|
179
|
+
status: number; // HTTP status code
|
|
180
|
+
statusText: string; // HTTP status text
|
|
181
|
+
headers: Record<string, string>; // Response headers
|
|
182
|
+
body?: unknown; // Parsed JSON body (if content-type is JSON)
|
|
183
|
+
text?: string; // Raw response text
|
|
184
|
+
};
|
|
185
|
+
timings: {
|
|
186
|
+
durationMs: number; // Total request duration in milliseconds
|
|
187
|
+
};
|
|
188
|
+
sizeBytes: number; // Response body size in bytes
|
|
299
189
|
}
|
|
300
190
|
```
|
|
301
191
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
| Field | Type | Default | Description |
|
|
305
|
-
|-------|------|---------|-------------|
|
|
306
|
-
| `url` | `string` | from `x-ws.url` | Absolute `ws://` or `wss://` URL. Overrides the spec. |
|
|
307
|
-
| `subprotocols` | `string[]` | `[]` | WebSocket subprotocols to negotiate. |
|
|
308
|
-
| `headers` | `Record<string,string>` | `{}` | Extra handshake headers. |
|
|
309
|
-
| `send` | `Array<string \| object \| Uint8Array>` | — | Messages sent immediately after open. |
|
|
310
|
-
| `sendDelayMs` | `number` | — | Delay between consecutive `send` messages. |
|
|
311
|
-
| `maxMessages` | `number` | — | Stop after this many inbound messages. |
|
|
312
|
-
| `maxSessionMs` | `number` | — | Hard cap on total session duration. |
|
|
313
|
-
| `idleTimeoutMs` | `number` | — | Close when no message arrives within this window. |
|
|
314
|
-
| `keepAlive` | `{ intervalMs, payload? }` | — | Application-level ping on an interval. |
|
|
315
|
-
| `closeCode` | `number` | `1000` | Close code sent when terminating. |
|
|
316
|
-
| `closeReason` | `string` | `"Closed"` | Close reason text. |
|
|
317
|
-
| `closeTimeoutMs` | `number` | — | Grace period for the peer's close frame before destroying the socket. |
|
|
318
|
-
| `rejectUnauthorized` | `boolean` | `true` | Reject self-signed TLS certificates. |
|
|
319
|
-
| `maxPayloadBytes` | `number` | — | Cap on retained payload size per frame. |
|
|
320
|
-
| `handshakeTimeoutMs` | `number` | `15000` | Handshake timeout. |
|
|
321
|
-
| `clientOptions` | `Record<string,unknown>` | `{}` | Extra options forwarded to the `ws` constructor. |
|
|
322
|
-
|
|
323
|
-
### Manual WebSocket session
|
|
324
|
-
|
|
325
|
-
```ts
|
|
326
|
-
const session = client.connect({
|
|
327
|
-
kind: "websocket",
|
|
328
|
-
url: "wss://api.example.com/ws",
|
|
329
|
-
subprotocols: ["chat"],
|
|
330
|
-
headers: { Authorization: "Bearer xxx" },
|
|
331
|
-
});
|
|
192
|
+
> **Important:** Response data is under `result.response`, not directly on `result`. Use `result.response.status`, `result.response.body`, and `result.response.headers`.
|
|
332
193
|
|
|
333
|
-
|
|
334
|
-
// e.kind: "open" | "text" | "binary" | "error" | "close" | "upgrade"
|
|
335
|
-
// e.direction: "in" | "out" | "meta"
|
|
336
|
-
// e.data: text payload (or base64 for binary)
|
|
337
|
-
});
|
|
194
|
+
### `PreparedRequest`
|
|
338
195
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
196
|
+
```typescript
|
|
197
|
+
interface PreparedRequest {
|
|
198
|
+
url: string; // Final URL with query string
|
|
199
|
+
method: string; // HTTP method
|
|
200
|
+
headers: Record<string, string>; // Resolved headers
|
|
201
|
+
body?: BodyInit; // Request body (if any)
|
|
202
|
+
operationId?: string; // Resolved operation ID
|
|
203
|
+
path: string; // Path template
|
|
204
|
+
parameters: ResolvedParameters;
|
|
205
|
+
}
|
|
342
206
|
```
|
|
343
207
|
|
|
344
208
|
---
|
|
345
209
|
|
|
346
|
-
##
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
```json
|
|
351
|
-
{
|
|
352
|
-
"paths": {
|
|
353
|
-
"/graphql": {
|
|
354
|
-
"post": {
|
|
355
|
-
"operationId": "graphqlQuery",
|
|
356
|
-
"x-protocol": "graphql",
|
|
357
|
-
"x-graphql": {
|
|
358
|
-
"endpoint": "https://api.example.com/graphql",
|
|
359
|
-
"operationType": "query",
|
|
360
|
-
"operationName": "GetUser",
|
|
361
|
-
"query": "query GetUser($id: ID!) { user(id: $id) { name } }",
|
|
362
|
-
"variablesSchema": {
|
|
363
|
-
"type": "object",
|
|
364
|
-
"properties": { "id": { "type": "string" } },
|
|
365
|
-
"required": ["id"]
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
"requestBody": {
|
|
369
|
-
"content": { "application/json": { "schema": { "type": "object" } } }
|
|
370
|
-
},
|
|
371
|
-
"responses": { "200": { "description": "ok" } }
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
```
|
|
210
|
+
## Parameter Serialization
|
|
211
|
+
|
|
212
|
+
All OpenAPI 3.2 parameter styles are supported:
|
|
377
213
|
|
|
378
|
-
|
|
214
|
+
| Location | Style | `explode: true` | `explode: false` |
|
|
215
|
+
|---|---|---|---|
|
|
216
|
+
| `path` | `simple` (default) | `id=5,role=admin` | `id,role=5,admin` |
|
|
217
|
+
| `path` | `label` | `.id=5.role=admin` | `.id,role=5,admin` |
|
|
218
|
+
| `path` | `matrix` | `;id=5;role=admin` | `;id,role=5,admin` |
|
|
219
|
+
| `query` | `form` (default) | `id=5&role=admin` | `id=5,role=admin` |
|
|
220
|
+
| `query` | `spaceDelimited` | `id=5%20role=admin` | `id=5%20role=admin` |
|
|
221
|
+
| `query` | `pipeDelimited` | `id=5\|role=admin` | `id=5\|role=admin` |
|
|
222
|
+
| `query` | `deepObject` | `user[id]=5&user[role]=admin` | — |
|
|
223
|
+
| `header` | `simple` (default) | `id=5,role=admin` | `id,role=5,admin` |
|
|
224
|
+
| `cookie` | `form` (default) | `id=5; role=admin` | `id=5,role=admin` |
|
|
225
|
+
|
|
226
|
+
---
|
|
379
227
|
|
|
380
|
-
|
|
381
|
-
|-------|------|---------|-------------|
|
|
382
|
-
| `endpoint` | `string` | from `x-graphql.endpoint` | Absolute HTTP(S) URL of the GraphQL endpoint. |
|
|
383
|
-
| `query` | `string` | from `x-graphql.query` | Query or mutation document. Overrides the spec. |
|
|
384
|
-
| `operationName` | `string` | from `x-graphql.operationName` | Required when `query` declares more than one operation. |
|
|
385
|
-
| `variables` | `Record<string,unknown>` | `{}` | GraphQL variables. Merged over sampled values. |
|
|
386
|
-
| `headers` | `Record<string,string>` | `{}` | Extra headers, merged over `values.header` and auth. |
|
|
387
|
-
| `useGet` | `boolean` | `false` | Use HTTP GET with querystring-encoded `query`/`variables` instead of POST. |
|
|
228
|
+
## Security Schemes
|
|
388
229
|
|
|
389
|
-
###
|
|
230
|
+
### Bearer token
|
|
390
231
|
|
|
391
|
-
```
|
|
392
|
-
const result = await client.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
232
|
+
```typescript
|
|
233
|
+
const result = await client.send({
|
|
234
|
+
spec,
|
|
235
|
+
operationId: "getUser",
|
|
236
|
+
securityValues: { bearerAuth: "YOUR_TOKEN" },
|
|
396
237
|
});
|
|
397
|
-
//
|
|
238
|
+
// Sends: Authorization: Bearer YOUR_TOKEN
|
|
398
239
|
```
|
|
399
240
|
|
|
400
|
-
|
|
241
|
+
### Basic auth
|
|
401
242
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
"/grpc": {
|
|
410
|
-
"post": {
|
|
411
|
-
"operationId": "grpcCall",
|
|
412
|
-
"x-protocol": "grpc",
|
|
413
|
-
"x-grpc": {
|
|
414
|
-
"address": "127.0.0.1:50051",
|
|
415
|
-
"service": "demo.echo.Echo",
|
|
416
|
-
"method": "Say",
|
|
417
|
-
"kind": "unary",
|
|
418
|
-
"reflection": true
|
|
419
|
-
},
|
|
420
|
-
"responses": { "200": { "description": "ok" } }
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
243
|
+
```typescript
|
|
244
|
+
const result = await client.send({
|
|
245
|
+
spec,
|
|
246
|
+
operationId: "getUser",
|
|
247
|
+
securityValues: { basicAuth: "username:password" },
|
|
248
|
+
});
|
|
249
|
+
// Sends: Authorization: Basic <base64(username:password)>
|
|
425
250
|
```
|
|
426
251
|
|
|
427
|
-
###
|
|
428
|
-
|
|
429
|
-
| Kind | Streaming | Interaction | Button label |
|
|
430
|
-
|------|-----------|-------------|-------------|
|
|
431
|
-
| `unary` | none | One request → one response. Session closes automatically. | "Send and await" |
|
|
432
|
-
| `server_streaming` | server → client | One request → N responses. Session closes on stream end. | "Send and await" |
|
|
433
|
-
| `client_streaming` | client → server | N requests → one response. Send messages, then Finish. | "Send message" + "Finish" |
|
|
434
|
-
| `bidi_streaming` | both | N requests ↔ N responses. Full duplex. | "Send message" + "Finish" |
|
|
435
|
-
|
|
436
|
-
### Manual gRPC session options
|
|
437
|
-
|
|
438
|
-
| Field | Type | Default | Description |
|
|
439
|
-
|-------|------|---------|-------------|
|
|
440
|
-
| `address` | `string` | **required** | `host:port`, no scheme. |
|
|
441
|
-
| `service` | `string` | **required** | Fully-qualified service name, e.g. `"demo.echo.Echo"`. |
|
|
442
|
-
| `method` | `string` | **required** | Method name as declared in proto. Matched case-insensitively as fallback. |
|
|
443
|
-
| `reflection` | `boolean` | — | Set `true` to use server reflection. Mutually exclusive with `protoPaths`. |
|
|
444
|
-
| `protoPaths` | `string[]` | — | `.proto` files or directories. Directories are walked recursively. |
|
|
445
|
-
| `includeDirs` | `string[]` | derived | Import roots for proto-loader. |
|
|
446
|
-
| `metadata` | `Record<string, string \| string[]>` | `{}` | gRPC metadata sent on every call. |
|
|
447
|
-
| `deadlineMs` | `number` | — | Per-call deadline. Enforced by the server (yields DEADLINE_EXCEEDED). |
|
|
448
|
-
| `channelOptions` | `Record<string,unknown>` | `{}` | grpc-js channel options. |
|
|
449
|
-
| `tls` | `boolean \| GrpcTlsOptions` | — | `false`/undefined = insecure; `true` = TLS with system roots; object = custom TLS. |
|
|
450
|
-
| `loaderOptions` | `Record<string,unknown>` | `{}` | Extra proto-loader options. |
|
|
451
|
-
| `reflectionTimeoutMs` | `number` | `5000` | Budget for the whole reflection session. |
|
|
452
|
-
| `reflectionVersion` | `"v1" \| "v1alpha"` | try v1, fall back | Pin a reflection version. |
|
|
453
|
-
| `reflectionHost` | `string` | — | `host` field on reflection requests (virtual-hosted servers). |
|
|
454
|
-
|
|
455
|
-
### gRPC TLS options
|
|
456
|
-
|
|
457
|
-
| Field | Type | Description |
|
|
458
|
-
|-------|------|-------------|
|
|
459
|
-
| `rootCerts` | `Buffer` | CA bundle contents (not a path). Pass `await readFile(p)`. |
|
|
460
|
-
| `privateKey` | `Buffer` | Client key for mTLS. Must be given with `certChain`. |
|
|
461
|
-
| `certChain` | `Buffer` | Client certificate chain for mTLS. Must be given with `privateKey`. |
|
|
462
|
-
| `skipHostnameVerification` | `boolean` | Skip hostname verification only. Chain is still verified. Always produces a warning. |
|
|
463
|
-
|
|
464
|
-
### gRPC session events
|
|
465
|
-
|
|
466
|
-
| `kind` | `direction` | Payload |
|
|
467
|
-
|--------|-------------|---------|
|
|
468
|
-
| `open` | `meta` | `{ address, service, method, kind, descriptorSource }` |
|
|
469
|
-
| `metadata` | `meta` | Response headers (initial metadata). |
|
|
470
|
-
| `data` | `out` | Outbound request message. |
|
|
471
|
-
| `data` | `in` | Inbound response message. |
|
|
472
|
-
| `status` | `meta` | Terminal status: `{ code, statusName, details, metadata }` (trailers). |
|
|
473
|
-
| `error` | `meta` | Error: `{ code, statusName, details, error }`. |
|
|
474
|
-
| `end` | `meta` | Stream ended. |
|
|
475
|
-
| `close` | `meta` | Session closed by the client. |
|
|
476
|
-
|
|
477
|
-
### gRPC example
|
|
478
|
-
|
|
479
|
-
```ts
|
|
480
|
-
const session = client.connect({
|
|
481
|
-
kind: "grpc",
|
|
482
|
-
address: "127.0.0.1:50051",
|
|
483
|
-
reflection: true,
|
|
484
|
-
service: "demo.echo.Echo",
|
|
485
|
-
method: "Say",
|
|
486
|
-
});
|
|
252
|
+
### API key (header)
|
|
487
253
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
254
|
+
```typescript
|
|
255
|
+
const result = await client.send({
|
|
256
|
+
spec,
|
|
257
|
+
operationId: "getUser",
|
|
258
|
+
securityValues: { apiKeyHeader: "YOUR_KEY" },
|
|
492
259
|
});
|
|
260
|
+
// Sends: X-API-Key: YOUR_KEY (header name from spec)
|
|
261
|
+
```
|
|
493
262
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
263
|
+
### API key (query)
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
const result = await client.send({
|
|
267
|
+
spec,
|
|
268
|
+
operationId: "getUser",
|
|
269
|
+
securityValues: { apiKeyQuery: "YOUR_KEY" },
|
|
270
|
+
});
|
|
271
|
+
// Appends: ?api_key=YOUR_KEY (param name from spec)
|
|
497
272
|
```
|
|
498
273
|
|
|
499
274
|
---
|
|
500
275
|
|
|
501
|
-
##
|
|
502
|
-
|
|
503
|
-
###
|
|
504
|
-
|
|
505
|
-
```
|
|
506
|
-
{
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
"x-mcp": {
|
|
514
|
-
"endpoint": "http://127.0.0.1:4200/mcp",
|
|
515
|
-
"method": "tools/call",
|
|
516
|
-
"name": "get_weather",
|
|
517
|
-
"arguments": { "city": "SF" }
|
|
518
|
-
},
|
|
519
|
-
"responses": { "200": { "description": "ok" } }
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
276
|
+
## Request Body Examples
|
|
277
|
+
|
|
278
|
+
### JSON
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const result = await client.send({
|
|
282
|
+
spec,
|
|
283
|
+
operationId: "createUser",
|
|
284
|
+
parameters: {
|
|
285
|
+
body: { name: "Ada", email: "ada@example.com" },
|
|
286
|
+
},
|
|
287
|
+
});
|
|
524
288
|
```
|
|
525
289
|
|
|
526
|
-
###
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
| `cwd` | `string` | — | Working directory for the stdio child. |
|
|
536
|
-
| `env` | `Record<string,string \| undefined>` | — | Environment for the stdio child. |
|
|
537
|
-
| `timeoutMs` | `number` | `30000` | Per-request timeout. `0` disables. |
|
|
538
|
-
| `maxBufferBytes` | `number` | — | stdio stdout buffer cap. |
|
|
539
|
-
| `maxStderrBytes` | `number` | — | stdio stderr cap before the child is killed. |
|
|
540
|
-
| `method` | `string` | from `x-mcp.method` | JSON-RPC method, e.g. `"tools/call"`. |
|
|
541
|
-
| `name` | `string` | from `x-mcp.name` | Target tool/prompt/resource name. |
|
|
542
|
-
| `arguments` | `Record<string,unknown>` | `{}` | JSON-RPC params. |
|
|
543
|
-
| `sessionId` | `string` | — | Reuse an existing MCP session ID. |
|
|
544
|
-
| `protocolVersion` | `string` | — | MCP protocol version to negotiate. |
|
|
545
|
-
| `clientInfo` | `{ name, version }` | `{ name: "powerduck", version: "0.1.0" }` | Client info sent in `initialize`. |
|
|
546
|
-
|
|
547
|
-
### Manual MCP session
|
|
548
|
-
|
|
549
|
-
```ts
|
|
550
|
-
const session = client.connect({
|
|
551
|
-
kind: "mcp",
|
|
552
|
-
transport: "streamable-http",
|
|
553
|
-
endpoint: "http://127.0.0.1:4200/mcp",
|
|
290
|
+
### Form URL-encoded
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
const result = await client.send({
|
|
294
|
+
spec,
|
|
295
|
+
operationId: "login",
|
|
296
|
+
parameters: {
|
|
297
|
+
body: { username: "ada", password: "secret" },
|
|
298
|
+
},
|
|
554
299
|
});
|
|
300
|
+
```
|
|
555
301
|
|
|
556
|
-
|
|
302
|
+
### Multipart Form Data
|
|
557
303
|
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
304
|
+
```typescript
|
|
305
|
+
const result = await client.send({
|
|
306
|
+
spec,
|
|
307
|
+
operationId: "uploadFile",
|
|
308
|
+
parameters: {
|
|
309
|
+
body: {
|
|
310
|
+
title: "My Document",
|
|
311
|
+
file: new File(["content"], "doc.pdf", { type: "application/pdf" }),
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
```
|
|
562
316
|
|
|
563
|
-
|
|
564
|
-
const result = await session.callTool("get_weather", { city: "SF" });
|
|
317
|
+
---
|
|
565
318
|
|
|
566
|
-
|
|
567
|
-
const raw = await session.request("tools/call", { name: "x", arguments: {} });
|
|
319
|
+
## Batch Requests
|
|
568
320
|
|
|
569
|
-
|
|
570
|
-
|
|
321
|
+
```typescript
|
|
322
|
+
import { createClient } from "@powerduck/openapi-request";
|
|
571
323
|
|
|
572
|
-
|
|
324
|
+
const client = createClient();
|
|
573
325
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
326
|
+
const results = await client.sendBatch([
|
|
327
|
+
{ spec, operationId: "getUser", parameters: { path: { id: "1" } } },
|
|
328
|
+
{ spec, operationId: "getUser", parameters: { path: { id: "2" } } },
|
|
329
|
+
{ spec, operationId: "getUser", parameters: { path: { id: "3" } } },
|
|
330
|
+
], { concurrency: 2 });
|
|
331
|
+
|
|
332
|
+
results.forEach((r, i) => {
|
|
333
|
+
if ("error" in r) {
|
|
334
|
+
console.log(`Request ${i} failed:`, r.error);
|
|
335
|
+
} else {
|
|
336
|
+
console.log(`Request ${i} status:`, r.response.status);
|
|
337
|
+
}
|
|
581
338
|
});
|
|
582
|
-
await session.open();
|
|
583
339
|
```
|
|
584
340
|
|
|
585
341
|
---
|
|
586
342
|
|
|
587
|
-
##
|
|
588
|
-
|
|
589
|
-
All manual sessions (WebSocket, gRPC, MCP) share this contract:
|
|
590
|
-
|
|
591
|
-
| Method / Property | Description |
|
|
592
|
-
|-------------------|-------------|
|
|
593
|
-
| `protocol` | `"websocket"` \| `"grpc"` \| `"mcp"` |
|
|
594
|
-
| `state` | `"idle"` \| `"connecting"` \| `"open"` \| `"closing"` \| `"closed"` \| `"error"` |
|
|
595
|
-
| `events` | Read-only array of all events (ring-buffered at 1000 by default). |
|
|
596
|
-
| `onEvent(listener)` | Subscribe to events. Returns `{ unsubscribe() }`. |
|
|
597
|
-
| `open()` | Connect / initialize. Rejects from non-idle states. |
|
|
598
|
-
| `send(message, options?)` | Send a message. Rejects when not open. |
|
|
599
|
-
| `close(options?)` | Close gracefully. Idempotent. |
|
|
600
|
-
| `waitForClose()` | Promise that resolves when the session closes. |
|
|
601
|
-
|
|
602
|
-
### SessionEventDTO
|
|
603
|
-
|
|
604
|
-
| Field | Type | Description |
|
|
605
|
-
|-------|------|-------------|
|
|
606
|
-
| `protocol` | `string` | Protocol name. |
|
|
607
|
-
| `transport` | `string` | Transport label. |
|
|
608
|
-
| `sessionId` | `string` | Unique session identifier. |
|
|
609
|
-
| `direction` | `"in" \| "out" \| "meta"` | Message direction. |
|
|
610
|
-
| `kind` | `string` | Event discriminator (protocol-specific). |
|
|
611
|
-
| `at` | `number` | Epoch ms timestamp. |
|
|
612
|
-
| `state` | `SessionState?` | Session state at the time of the event. |
|
|
613
|
-
| `data` | `Cloneable?` | Message payload. |
|
|
614
|
-
| `error` | `Cloneable?` | Error payload. |
|
|
615
|
-
| `meta` | `Cloneable?` | Metadata (headers, status code, etc.). |
|
|
343
|
+
## Postman Scripts
|
|
616
344
|
|
|
617
|
-
|
|
345
|
+
Run Postman test scripts embedded in `x-postman-scripts`:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { createClient, runPostmanScripts } from "@powerduck/openapi-request";
|
|
349
|
+
|
|
350
|
+
const client = createClient();
|
|
351
|
+
const result = await client.send({
|
|
352
|
+
spec,
|
|
353
|
+
operationId: "listUsers",
|
|
354
|
+
});
|
|
618
355
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
| `response.contentType` | `string?` | Parsed content type. |
|
|
629
|
-
| `response.body` | `unknown` | Parsed body (non-streaming). |
|
|
630
|
-
| `response.text` | `string?` | Raw body text. |
|
|
631
|
-
| `response.events` | `StreamEvent[]` | Collected streaming events. |
|
|
632
|
-
| `response.timings` | `{ startedAt, endedAt, durationMs, firstByteMs?, networkDurationMs? }` | Timing data. |
|
|
633
|
-
| `response.sizeBytes` | `number` | Body bytes received. |
|
|
634
|
-
| `response.truncated` | `boolean?` | True if sampling stopped before natural completion. |
|
|
635
|
-
| `response.stopReason` | `StopReason?` | Which limit fired: `"maxEvents" \| "maxStreamMs" \| "maxResponseSize" \| "aborted" \| "hardTimeout"`. |
|
|
636
|
-
| `response.droppedEvents` | `number?` | Events discarded by the parser's size caps. |
|
|
637
|
-
| `scripts` | `ScriptReport?` | Pre-request / test script results. |
|
|
638
|
-
| `cookies` | `Array<{name, value, domain?, path?}>` | Cookies set by the response. |
|
|
639
|
-
| `replays` | `ReplayRecord[]` | Auxiliary traffic (OAuth refreshes, redirects). |
|
|
640
|
-
| `error` | `{ message, code?, name?, stack? }?` | Fatal error, if any. |
|
|
641
|
-
| `collection` | `any` | Generated Postman collection (HTTP only). |
|
|
642
|
-
| `environment` | `any` | Generated Postman environment. |
|
|
643
|
-
| `responseFragment` | `any` | OpenAPI Response object derived from the live call. |
|
|
644
|
-
| `responseStatusCode` | `string` | Status code the fragment was filed under. |
|
|
645
|
-
| `patchedSpec` | `OpenApiDocument?` | Spec with the response merged in. |
|
|
646
|
-
| `writeBackSkippedReason` | `string?` | Why write-back was skipped. |
|
|
356
|
+
// Run pm.test() scripts from the operation's x-postman-scripts
|
|
357
|
+
const testResults = runPostmanScripts(spec, "listUsers", result.response);
|
|
358
|
+
|
|
359
|
+
console.log("Passed:", testResults.passed);
|
|
360
|
+
console.log("Failed:", testResults.failed);
|
|
361
|
+
for (const test of testResults.tests) {
|
|
362
|
+
console.log(`${test.passed ? "✓" : "✗"} ${test.name}`);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
647
365
|
|
|
648
366
|
---
|
|
649
367
|
|
|
650
368
|
## Error Handling
|
|
651
369
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
| Code | Meaning |
|
|
655
|
-
|------|---------|
|
|
656
|
-
| `BAD_OPTIONS` | Missing or invalid send options. |
|
|
657
|
-
| `BAD_RUN_OPTIONS` | Invalid runtime options (e.g. `maxResponseSize: 0`). |
|
|
658
|
-
| `BAD_COLLECTION` | Failed to construct the Postman collection. |
|
|
659
|
-
| `RUNTIME_INIT` | postman-runtime failed to initialize. |
|
|
660
|
-
| `RUNTIME_RUN` | The run failed with a done error. |
|
|
661
|
-
| `EXECUTION_FAILED` | A protocol adapter threw. |
|
|
662
|
-
| `BAD_MCP_ENDPOINT` | Invalid MCP endpoint URL. |
|
|
663
|
-
| `BAD_MCP_STDIO_COMMAND` | Missing stdio command. |
|
|
664
|
-
| `MCP_TIMEOUT` | MCP request or initialize timed out. |
|
|
665
|
-
| `MCP_NOT_OPEN` | Called before `open()`. |
|
|
666
|
-
| `MCP_SESSION_CLOSED` | Called on a closed session. |
|
|
667
|
-
| `MCP_SESSION_EXPIRED` | Server returned 404 for the session. |
|
|
668
|
-
| `MCP_RPC_ERROR` | JSON-RPC error response. |
|
|
669
|
-
| `MCP_EMPTY_RESPONSE` | No JSON-RPC message in the response. |
|
|
670
|
-
|
|
671
|
-
```ts
|
|
672
|
-
import { ProtoKitError } from "@powerduck/openapi-request";
|
|
673
|
-
|
|
370
|
+
```typescript
|
|
674
371
|
try {
|
|
675
|
-
await client.send(
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
372
|
+
const result = await client.send({
|
|
373
|
+
spec,
|
|
374
|
+
operationId: "getUser",
|
|
375
|
+
parameters: { path: { id: "123" } },
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (result.response.status >= 400) {
|
|
379
|
+
console.error("API error:", result.response.status, result.response.body);
|
|
380
|
+
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
if (error.name === "AbortError") {
|
|
383
|
+
console.error("Request timed out or was aborted");
|
|
384
|
+
} else if (error.name === "OpenApiRequestError") {
|
|
385
|
+
console.error("Request preparation failed:", error.message);
|
|
386
|
+
} else {
|
|
387
|
+
console.error("Network error:", error);
|
|
679
388
|
}
|
|
680
389
|
}
|
|
681
390
|
```
|
|
@@ -685,26 +394,33 @@ try {
|
|
|
685
394
|
## Development
|
|
686
395
|
|
|
687
396
|
```bash
|
|
397
|
+
# Install dependencies
|
|
688
398
|
npm install
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
npm run typecheck
|
|
399
|
+
|
|
400
|
+
# Type check
|
|
401
|
+
npm run typecheck
|
|
402
|
+
|
|
403
|
+
# Build (ESM + CJS + type declarations)
|
|
404
|
+
npm run build
|
|
405
|
+
|
|
406
|
+
# Run tests
|
|
407
|
+
npm test
|
|
408
|
+
|
|
409
|
+
# Watch mode
|
|
410
|
+
npx vitest watch
|
|
692
411
|
```
|
|
693
412
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
| `tests/integration.test.ts` | 4 tests — end-to-end HTTP + SSE streaming. |
|
|
706
|
-
| `tests/clone.test.ts` | 8 tests — payload cloning. |
|
|
707
|
-
| `tests/detect.test.ts` | 4 tests — SSE content-type detection. |
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Related Packages
|
|
416
|
+
|
|
417
|
+
- [`@powerduck/openapi-parser`](https://www.npmjs.com/package/@powerduck/openapi-parser) — OpenAPI 3.2 parser, validator, and upgrader
|
|
418
|
+
- [`@powerduck/openapi-codegen`](https://www.npmjs.com/package/@powerduck/openapi-codegen) — Generate runnable request examples in 21 languages
|
|
419
|
+
- [`@powerduck/openapi-cli`](https://www.npmjs.com/package/@powerduck/openapi-cli) — CI-ready batch testing for OpenAPI documents
|
|
420
|
+
- [`@powerduck/openapi-mcp-server`](https://www.npmjs.com/package/@powerduck/openapi-mcp-server) — Turn OpenAPI docs into MCP servers
|
|
421
|
+
- [`@powerduck/x-to-openapi`](https://www.npmjs.com/package/@powerduck/x-to-openapi) — Convert curl commands and Postman Collections to OpenAPI 3.2
|
|
422
|
+
|
|
423
|
+
---
|
|
708
424
|
|
|
709
425
|
## License
|
|
710
426
|
|
package/package.json
CHANGED