@superblocksteam/sdk-api 2.0.142-next.1 → 2.0.142-next.2
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/dist/integrations/base/index.d.ts +1 -1
- package/dist/integrations/base/index.d.ts.map +1 -1
- package/dist/integrations/base/rest-api-client-base.d.ts +1 -1
- package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
- package/dist/integrations/base/rest-api-client-base.js +23 -3
- package/dist/integrations/base/rest-api-client-base.js.map +1 -1
- package/dist/integrations/base/rest-api-integration-client.d.ts +9 -3
- package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
- package/dist/integrations/base/rest-api-integration-client.js +31 -3
- package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
- package/dist/integrations/base/types.d.ts +69 -3
- package/dist/integrations/base/types.d.ts.map +1 -1
- package/dist/integrations/base/types.js +23 -1
- package/dist/integrations/base/types.js.map +1 -1
- package/dist/integrations/documentation-resolver.test.js +24 -0
- package/dist/integrations/documentation-resolver.test.js.map +1 -1
- package/dist/integrations/restapiintegration/client.test.d.ts +12 -0
- package/dist/integrations/restapiintegration/client.test.d.ts.map +1 -0
- package/dist/integrations/restapiintegration/client.test.js +236 -0
- package/dist/integrations/restapiintegration/client.test.js.map +1 -0
- package/package.json +3 -2
- package/src/integrations/base/index.ts +1 -0
- package/src/integrations/base/rest-api-client-base.ts +27 -3
- package/src/integrations/base/rest-api-integration-client.ts +54 -5
- package/src/integrations/base/types.ts +77 -3
- package/src/integrations/documentation-resolver.test.ts +31 -0
- package/src/integrations/restapiintegration/client.test.ts +359 -0
- package/src/integrations/restapiintegration/docs.manifest.json +10 -1
- package/src/integrations/restapiintegration/overlays/response-types-unsupported.md +5 -0
- package/src/integrations/restapiintegration/overlays/response-types.md +26 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for RestApiIntegrationPluginClientImpl (generic REST API Integration).
|
|
3
|
+
*
|
|
4
|
+
* Covers the responseType passthrough:
|
|
5
|
+
* - default requests keep responseType "json"; callers that pass a
|
|
6
|
+
* response schema still get validated results
|
|
7
|
+
* - the responseType option is forwarded to the orchestrator request
|
|
8
|
+
* - non-JSON responses (e.g. XML with responseType "text") are returned
|
|
9
|
+
* raw when no response schema is provided
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, vi } from "vitest";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { RestApiValidationError } from "../../errors.js";
|
|
14
|
+
import { RestApiIntegrationPluginClientImpl } from "./client.js";
|
|
15
|
+
// ── Wire-contract assertions (verified by `pnpm typecheck`) ────────
|
|
16
|
+
//
|
|
17
|
+
// The responseType strings are sent to the orchestrator as-is; the
|
|
18
|
+
// accepted values are defined by ActionResponseType. This assertion
|
|
19
|
+
// fails compilation if the SDK union drifts outside the wire values.
|
|
20
|
+
//
|
|
21
|
+
// The SDK union is a deliberate subset of the wire values: "auto" and
|
|
22
|
+
// "binary" are implemented by the orchestrator but not exposed (see the
|
|
23
|
+
// RestApiResponseType doc for why), and "raw" is streaming-only.
|
|
24
|
+
const _sdkValuesAreValidWireValues = [];
|
|
25
|
+
void _sdkValuesAreValidWireValues;
|
|
26
|
+
const TEST_CONFIG = {
|
|
27
|
+
id: "restapi-test-id",
|
|
28
|
+
name: "Test REST API",
|
|
29
|
+
pluginId: "restapiintegration",
|
|
30
|
+
configuration: {},
|
|
31
|
+
};
|
|
32
|
+
const XML_RESPONSE = `<?xml version="1.0"?><note><body>hi</body></note>`;
|
|
33
|
+
function createClient(mockResult) {
|
|
34
|
+
const executeQuery = vi.fn().mockResolvedValue(mockResult);
|
|
35
|
+
const client = new RestApiIntegrationPluginClientImpl(TEST_CONFIG, executeQuery);
|
|
36
|
+
return { client, executeQuery };
|
|
37
|
+
}
|
|
38
|
+
describe("RestApiIntegrationPluginClientImpl", () => {
|
|
39
|
+
// ── Default JSON behavior (must not regress) ────────────────────
|
|
40
|
+
describe("default JSON behavior", () => {
|
|
41
|
+
it("sends responseType json when the option is omitted", async () => {
|
|
42
|
+
const { client, executeQuery } = createClient({ id: "1" });
|
|
43
|
+
await client.apiRequest({ method: "GET", path: "/users" }, { response: z.object({ id: z.string() }) });
|
|
44
|
+
expect(executeQuery).toHaveBeenCalledWith(expect.objectContaining({ responseType: "json" }), undefined, undefined);
|
|
45
|
+
});
|
|
46
|
+
it("validates the response against the provided schema", async () => {
|
|
47
|
+
const { client } = createClient({ id: "1", extra: "stripped" });
|
|
48
|
+
const result = await client.apiRequest({ method: "GET", path: "/users" }, { response: z.object({ id: z.string() }) });
|
|
49
|
+
expect(result).toEqual({ id: "1" });
|
|
50
|
+
});
|
|
51
|
+
it("throws RestApiValidationError when the response does not match the schema", async () => {
|
|
52
|
+
const { client } = createClient(XML_RESPONSE);
|
|
53
|
+
await expect(client.apiRequest({ method: "GET", path: "/users" }, { response: z.object({ id: z.string() }) })).rejects.toThrow(RestApiValidationError);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
// ── responseType passthrough ────────────────────────────────────
|
|
57
|
+
describe("responseType passthrough", () => {
|
|
58
|
+
it("forwards responseType text to the orchestrator request", async () => {
|
|
59
|
+
const { client, executeQuery } = createClient(XML_RESPONSE);
|
|
60
|
+
await client.apiRequest({
|
|
61
|
+
method: "GET",
|
|
62
|
+
path: "/report.xml",
|
|
63
|
+
responseType: "text",
|
|
64
|
+
});
|
|
65
|
+
expect(executeQuery).toHaveBeenCalledWith(expect.objectContaining({ responseType: "text" }), undefined, undefined);
|
|
66
|
+
});
|
|
67
|
+
it("returns a raw XML string with responseType text and no response schema", async () => {
|
|
68
|
+
const { client } = createClient(XML_RESPONSE);
|
|
69
|
+
const result = await client.apiRequest({
|
|
70
|
+
method: "GET",
|
|
71
|
+
path: "/report.xml",
|
|
72
|
+
responseType: "text",
|
|
73
|
+
});
|
|
74
|
+
expect(result).toBe(XML_RESPONSE);
|
|
75
|
+
});
|
|
76
|
+
it("still validates when a response schema is provided alongside responseType", async () => {
|
|
77
|
+
const { client, executeQuery } = createClient(XML_RESPONSE);
|
|
78
|
+
const result = await client.apiRequest({ method: "GET", path: "/report.xml", responseType: "text" }, { response: z.string() });
|
|
79
|
+
expect(executeQuery).toHaveBeenCalledWith(expect.objectContaining({ responseType: "text" }), undefined, undefined);
|
|
80
|
+
expect(result).toBe(XML_RESPONSE);
|
|
81
|
+
});
|
|
82
|
+
it("returns the raw response without validation when no schema is given", async () => {
|
|
83
|
+
const { client } = createClient({ ok: true, rows: [1, 2, 3] });
|
|
84
|
+
const result = await client.apiRequest({
|
|
85
|
+
method: "GET",
|
|
86
|
+
path: "/data",
|
|
87
|
+
responseType: "text",
|
|
88
|
+
});
|
|
89
|
+
expect(result).toEqual({ ok: true, rows: [1, 2, 3] });
|
|
90
|
+
});
|
|
91
|
+
it("returns an empty string unchanged for responseType text", async () => {
|
|
92
|
+
// An empty text body legitimately decodes to "" — the null/undefined
|
|
93
|
+
// guard must not reject it.
|
|
94
|
+
const { client } = createClient("");
|
|
95
|
+
const result = await client.apiRequest({
|
|
96
|
+
method: "GET",
|
|
97
|
+
path: "/empty",
|
|
98
|
+
responseType: "text",
|
|
99
|
+
});
|
|
100
|
+
expect(result).toBe("");
|
|
101
|
+
});
|
|
102
|
+
it("rejects null and undefined results for every response type", async () => {
|
|
103
|
+
// Neither value is a legitimate decode result for the exposed
|
|
104
|
+
// response types: JSON parses to a value the schema sees, and an
|
|
105
|
+
// empty text body decodes to "". Either means a broken execution
|
|
106
|
+
// contract, so both throw.
|
|
107
|
+
for (const badResult of [null, undefined]) {
|
|
108
|
+
const { client: textClient } = createClient(badResult);
|
|
109
|
+
await expect(textClient.apiRequest({
|
|
110
|
+
method: "GET",
|
|
111
|
+
path: "/broken",
|
|
112
|
+
responseType: "text",
|
|
113
|
+
})).rejects.toThrow(RestApiValidationError);
|
|
114
|
+
// JSON mode always goes through the schema overload; the bad
|
|
115
|
+
// result is rejected before validation runs.
|
|
116
|
+
const { client: jsonClient } = createClient(badResult);
|
|
117
|
+
await expect(jsonClient.apiRequest({ method: "GET", path: "/broken" }, { response: z.object({}) })).rejects.toThrow(RestApiValidationError);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
// ── Request-body validation via the schema-less overload ────────
|
|
122
|
+
describe("request body validation", () => {
|
|
123
|
+
it("throws RestApiValidationError for an invalid body with a body-only schema", async () => {
|
|
124
|
+
const { client, executeQuery } = createClient({ ok: true });
|
|
125
|
+
// Runtime-invalid bodies reach this path via dynamic data (user
|
|
126
|
+
// input, parsed JSON) that the compiler cannot check.
|
|
127
|
+
const dynamicBody = JSON.parse('{"amount":"not-a-number"}');
|
|
128
|
+
await expect(client.apiRequest({
|
|
129
|
+
method: "POST",
|
|
130
|
+
path: "/orders",
|
|
131
|
+
body: dynamicBody,
|
|
132
|
+
responseType: "text",
|
|
133
|
+
}, { body: z.object({ amount: z.number() }) })).rejects.toThrow(RestApiValidationError);
|
|
134
|
+
expect(executeQuery).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
it("sends a valid body and returns the raw response with a body-only schema", async () => {
|
|
137
|
+
const { client, executeQuery } = createClient({ ok: true });
|
|
138
|
+
const result = await client.apiRequest({
|
|
139
|
+
method: "POST",
|
|
140
|
+
path: "/orders",
|
|
141
|
+
body: { amount: 42 },
|
|
142
|
+
responseType: "text",
|
|
143
|
+
}, { body: z.object({ amount: z.number() }) });
|
|
144
|
+
expect(executeQuery).toHaveBeenCalledWith(expect.objectContaining({
|
|
145
|
+
body: JSON.stringify({ amount: 42 }),
|
|
146
|
+
bodyType: "jsonBody",
|
|
147
|
+
}), undefined, undefined);
|
|
148
|
+
expect(result).toEqual({ ok: true });
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
// ── Runtime contract enforcement ─────────────────────────────────
|
|
152
|
+
//
|
|
153
|
+
// The overloads make these calls unrepresentable in TypeScript, but
|
|
154
|
+
// user API code executes as esbuild-bundled JS with no type
|
|
155
|
+
// enforcement — the same contract must hold at runtime.
|
|
156
|
+
describe("runtime contract enforcement", () => {
|
|
157
|
+
it("rejects a schema-less call in default JSON mode without issuing the request", async () => {
|
|
158
|
+
const { client, executeQuery } = createClient({ ok: true });
|
|
159
|
+
await expect(client.apiRequest({
|
|
160
|
+
method: "GET",
|
|
161
|
+
path: "/users",
|
|
162
|
+
})).rejects.toThrow(RestApiValidationError);
|
|
163
|
+
expect(executeQuery).not.toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
it("rejects a body-only schema in explicit JSON mode without issuing the request", async () => {
|
|
166
|
+
const { client, executeQuery } = createClient({ ok: true });
|
|
167
|
+
await expect(client.apiRequest({
|
|
168
|
+
method: "POST",
|
|
169
|
+
path: "/orders",
|
|
170
|
+
body: { amount: 42 },
|
|
171
|
+
responseType: "json",
|
|
172
|
+
}, { body: z.object({ amount: z.number() }) })).rejects.toThrow(RestApiValidationError);
|
|
173
|
+
expect(executeQuery).not.toHaveBeenCalled();
|
|
174
|
+
});
|
|
175
|
+
it("rejects an unexposed responseType value without issuing the request", async () => {
|
|
176
|
+
const { client, executeQuery } = createClient({ ok: true });
|
|
177
|
+
await expect(client.apiRequest({
|
|
178
|
+
method: "GET",
|
|
179
|
+
path: "/report",
|
|
180
|
+
responseType: "auto",
|
|
181
|
+
})).rejects.toThrow(RestApiValidationError);
|
|
182
|
+
expect(executeQuery).not.toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
// ── Compile-time overload contracts (verified by `pnpm typecheck`) ──
|
|
186
|
+
describe("overload resolution", () => {
|
|
187
|
+
it("keeps typed results for schema callers and unknown for schema-less calls", async () => {
|
|
188
|
+
const { client } = createClient({ id: "1" });
|
|
189
|
+
// With a response schema: resolves to the typed overload.
|
|
190
|
+
const typed = client.apiRequest({ method: "GET", path: "/users" }, { response: z.object({ id: z.string() }) });
|
|
191
|
+
expect(await typed).toEqual({ id: "1" });
|
|
192
|
+
const { client: rawClient } = createClient(XML_RESPONSE);
|
|
193
|
+
// Without a schema: resolves to the raw overload returning unknown.
|
|
194
|
+
const raw = rawClient.apiRequest({
|
|
195
|
+
method: "GET",
|
|
196
|
+
path: "/report.xml",
|
|
197
|
+
responseType: "text",
|
|
198
|
+
});
|
|
199
|
+
expect(await raw).toBe(XML_RESPONSE);
|
|
200
|
+
// @ts-expect-error schema-less apiRequest returns unknown, not a typed shape
|
|
201
|
+
const wrong = rawClient.apiRequest({
|
|
202
|
+
method: "GET",
|
|
203
|
+
path: "/report.xml",
|
|
204
|
+
responseType: "text",
|
|
205
|
+
});
|
|
206
|
+
await wrong;
|
|
207
|
+
// The remaining cases are rejected twice over: at compile time by
|
|
208
|
+
// the overloads (@ts-expect-error) and at runtime by the contract
|
|
209
|
+
// guards, since bundled user code carries no types.
|
|
210
|
+
// @ts-expect-error unvalidated JSON is unrepresentable — schema-less
|
|
211
|
+
// calls must opt into a non-JSON responseType
|
|
212
|
+
const unvalidatedJson = rawClient.apiRequest({
|
|
213
|
+
method: "GET",
|
|
214
|
+
path: "/users",
|
|
215
|
+
});
|
|
216
|
+
await expect(unvalidatedJson).rejects.toThrow(RestApiValidationError);
|
|
217
|
+
// "auto" and "binary" are valid wire values but deliberately not
|
|
218
|
+
// exposed in the SDK union (see RestApiResponseType).
|
|
219
|
+
const autoCall = rawClient.apiRequest({
|
|
220
|
+
method: "GET",
|
|
221
|
+
path: "/report.xml",
|
|
222
|
+
// @ts-expect-error "auto" is not an exposed responseType
|
|
223
|
+
responseType: "auto",
|
|
224
|
+
});
|
|
225
|
+
await expect(autoCall).rejects.toThrow(RestApiValidationError);
|
|
226
|
+
const binaryCall = rawClient.apiRequest({
|
|
227
|
+
method: "GET",
|
|
228
|
+
path: "/report.xml",
|
|
229
|
+
// @ts-expect-error "binary" is not an exposed responseType
|
|
230
|
+
responseType: "binary",
|
|
231
|
+
});
|
|
232
|
+
await expect(binaryCall).rejects.toThrow(RestApiValidationError);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
//# sourceMappingURL=client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../../src/integrations/restapiintegration/client.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superblocksteam/sdk-api",
|
|
3
|
-
"version": "2.0.142-next.
|
|
3
|
+
"version": "2.0.142-next.2",
|
|
4
4
|
"description": "Superblocks SDK for TypeScript-based API definitions",
|
|
5
5
|
"license": "Superblocks Community Software License",
|
|
6
6
|
"files": [
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"typescript": "^6.0.3",
|
|
31
31
|
"typescript-eslint": "^8.59.2",
|
|
32
32
|
"uuid": "^11.1.1",
|
|
33
|
-
"vitest": "^2.0.0"
|
|
33
|
+
"vitest": "^2.0.0",
|
|
34
|
+
"@superblocksteam/shared": "0.9601.0"
|
|
34
35
|
},
|
|
35
36
|
"engines": {
|
|
36
37
|
"node": ">=20",
|
|
@@ -17,6 +17,7 @@ import type { Plugin as RestApiIntegrationPlugin } from "@superblocksteam/types/
|
|
|
17
17
|
import { RestApiValidationError } from "../../errors.js";
|
|
18
18
|
import type { QueryExecutor, TraceMetadata } from "../registry.js";
|
|
19
19
|
import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
|
|
20
|
+
import { REST_API_RESPONSE_TYPES } from "./types.js";
|
|
20
21
|
import type { ApiRequestOptions } from "./types.js";
|
|
21
22
|
|
|
22
23
|
export type RestApiRequest = PartialMessage<RestApiIntegrationPlugin>;
|
|
@@ -58,7 +59,7 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
|
|
|
58
59
|
* Returns the raw (unvalidated) response from the orchestrator.
|
|
59
60
|
* Subclasses call this, then apply their own response handling.
|
|
60
61
|
*
|
|
61
|
-
* @param options -
|
|
62
|
+
* @param options - Request configuration (see {@link ApiRequestOptions})
|
|
62
63
|
* @param bodySchema - Optional Zod schema for body validation
|
|
63
64
|
* @param metadata - Optional trace metadata for observability
|
|
64
65
|
* @returns Raw response from the orchestrator
|
|
@@ -96,13 +97,31 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
|
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
// The RestApiResponseType union is erased in the bundled JS user APIs
|
|
101
|
+
// run as, so values outside it (including wire values the orchestrator
|
|
102
|
+
// implements but the SDK deliberately does not expose, like "auto")
|
|
103
|
+
// must also be rejected at runtime, before the request is issued.
|
|
104
|
+
const responseTypeResult = z
|
|
105
|
+
.enum(REST_API_RESPONSE_TYPES)
|
|
106
|
+
.safeParse(options.responseType ?? "json");
|
|
107
|
+
if (!responseTypeResult.success) {
|
|
108
|
+
throw new RestApiValidationError(
|
|
109
|
+
`Unsupported responseType ${JSON.stringify(options.responseType)} — expected one of: ${REST_API_RESPONSE_TYPES.join(", ")}`,
|
|
110
|
+
{
|
|
111
|
+
zodError: responseTypeResult.error,
|
|
112
|
+
data: options.responseType,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const responseType = responseTypeResult.data;
|
|
117
|
+
|
|
99
118
|
const request: RestApiRequest = {
|
|
100
119
|
openApiAction: "genericHttpRequest",
|
|
101
120
|
httpMethod: options.method.toUpperCase(),
|
|
102
121
|
urlPath: options.path,
|
|
103
122
|
headers,
|
|
104
123
|
params,
|
|
105
|
-
responseType
|
|
124
|
+
responseType,
|
|
106
125
|
};
|
|
107
126
|
|
|
108
127
|
if (options.body !== undefined) {
|
|
@@ -116,11 +135,16 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
|
|
|
116
135
|
metadata,
|
|
117
136
|
);
|
|
118
137
|
|
|
138
|
+
// Neither `null` nor `undefined` is a legitimate decode result for the
|
|
139
|
+
// exposed response types: JSON parses to a value the schema sees, and
|
|
140
|
+
// an empty text body decodes to "". Either value only arises from a
|
|
141
|
+
// broken execution contract, so both throw. (If "auto" is ever exposed,
|
|
142
|
+
// `null` becomes legitimate — it parses a literal `null` JSON body.)
|
|
119
143
|
if (result === null || result === undefined) {
|
|
120
144
|
const nonNullResult = z.object({}).safeParse(result);
|
|
121
145
|
if (!nonNullResult.success) {
|
|
122
146
|
throw new RestApiValidationError(
|
|
123
|
-
`Integration query returned ${String(result)} — expected a
|
|
147
|
+
`Integration query returned ${String(result)} for responseType "${responseType}" — expected a response value`,
|
|
124
148
|
{
|
|
125
149
|
zodError: nonNullResult.error,
|
|
126
150
|
data: result,
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Extends RestApiClientBase with an apiRequest() that validates the
|
|
5
5
|
* full response against a caller-supplied Zod schema and throws
|
|
6
|
-
* RestApiValidationError on mismatch.
|
|
6
|
+
* RestApiValidationError on mismatch. When no response schema is
|
|
7
|
+
* provided, the raw result is returned without validation — useful for
|
|
8
|
+
* non-JSON responses (e.g. XML), which can alternatively be validated
|
|
9
|
+
* with a schema matching the decoded value (e.g. z.string()).
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
|
-
import
|
|
12
|
+
import { z } from "zod";
|
|
10
13
|
|
|
11
14
|
import { RestApiValidationError } from "../../errors.js";
|
|
12
15
|
import type { TraceMetadata } from "../registry.js";
|
|
@@ -14,6 +17,7 @@ import { RestApiClientBase } from "./rest-api-client-base.js";
|
|
|
14
17
|
import type {
|
|
15
18
|
ApiRequestOptions,
|
|
16
19
|
ApiRequestSchema,
|
|
20
|
+
RestApiResponseType,
|
|
17
21
|
SupportsApiRequest,
|
|
18
22
|
} from "./types.js";
|
|
19
23
|
|
|
@@ -34,10 +38,55 @@ export abstract class RestApiIntegrationClient
|
|
|
34
38
|
response: z.ZodSchema<TResponse>;
|
|
35
39
|
},
|
|
36
40
|
metadata?: TraceMetadata,
|
|
37
|
-
): Promise<TResponse
|
|
38
|
-
|
|
41
|
+
): Promise<TResponse>;
|
|
42
|
+
async apiRequest<TBody>(
|
|
43
|
+
options: ApiRequestOptions<TBody> & {
|
|
44
|
+
responseType: Exclude<RestApiResponseType, "json">;
|
|
45
|
+
},
|
|
46
|
+
schema?: ApiRequestSchema<TBody, unknown>,
|
|
47
|
+
metadata?: TraceMetadata,
|
|
48
|
+
): Promise<unknown>;
|
|
49
|
+
async apiRequest<TBody, TResponse>(
|
|
50
|
+
options: ApiRequestOptions<TBody>,
|
|
51
|
+
schema?: ApiRequestSchema<TBody, TResponse>,
|
|
52
|
+
metadata?: TraceMetadata,
|
|
53
|
+
): Promise<TResponse | unknown> {
|
|
54
|
+
// The overloads make schema-less JSON unrepresentable in TypeScript,
|
|
55
|
+
// but user API code executes as bundled JS with no type enforcement —
|
|
56
|
+
// enforce the same contract at runtime, before issuing the request.
|
|
57
|
+
if ((options.responseType ?? "json") === "json" && !schema?.response) {
|
|
58
|
+
throw new RestApiValidationError(
|
|
59
|
+
'apiRequest() with responseType "json" requires a response schema — provide one, or set responseType: "text" to receive a non-JSON payload unvalidated',
|
|
60
|
+
{
|
|
61
|
+
zodError: new z.ZodError([
|
|
62
|
+
{
|
|
63
|
+
code: z.ZodIssueCode.custom,
|
|
64
|
+
message: "response schema is required for JSON responses",
|
|
65
|
+
path: ["response"],
|
|
66
|
+
},
|
|
67
|
+
]),
|
|
68
|
+
data: undefined,
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const result = await this.executeApiRequest(
|
|
74
|
+
options,
|
|
75
|
+
schema?.body,
|
|
76
|
+
metadata,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Without a response schema the raw result is returned as-is (typed
|
|
80
|
+
// unknown). Non-JSON responses (e.g. responseType "text" returning
|
|
81
|
+
// XML) can either omit the schema or pass one matching the decoded
|
|
82
|
+
// value, such as z.string(). Request-body validation is opt-in:
|
|
83
|
+
// executeApiRequest only validates options.body when schema.body is
|
|
84
|
+
// provided (unchanged from the previous contract, where schema.body
|
|
85
|
+
// was already optional).
|
|
86
|
+
if (!schema?.response) {
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
39
89
|
|
|
40
|
-
// Response schema is REQUIRED - always validate
|
|
41
90
|
const responseParseResult = schema.response.safeParse(result);
|
|
42
91
|
|
|
43
92
|
if (!responseParseResult.success) {
|
|
@@ -9,6 +9,32 @@ import type { TraceMetadata } from "../registry.js";
|
|
|
9
9
|
// Re-export for backwards compatibility
|
|
10
10
|
export type { TraceMetadata };
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* How the orchestrator should decode the HTTP response body.
|
|
14
|
+
*
|
|
15
|
+
* - `"json"` (default): parse the response as JSON; fails on non-JSON bodies
|
|
16
|
+
* - `"text"`: return the decoded response body as a string (XML, CSV, HTML, ...)
|
|
17
|
+
*
|
|
18
|
+
* The orchestrator also implements `"auto"`, `"binary"`, and `"raw"` wire
|
|
19
|
+
* values; they are deliberately not exposed here (widening this union later
|
|
20
|
+
* is backward-compatible, narrowing it is not):
|
|
21
|
+
*
|
|
22
|
+
* - `"auto"` returns parsed JSON whenever the body happens to parse, which
|
|
23
|
+
* would let JSON responses bypass schema validation via the schema-less
|
|
24
|
+
* overload
|
|
25
|
+
* - `"binary"` would freeze the worker's internal Buffer JSON encoding
|
|
26
|
+
* (`{ type: "Buffer", data: number[] }`, ~4x payload inflation) into the
|
|
27
|
+
* public SDK contract
|
|
28
|
+
* - `"raw"` is rejected by the orchestrator for non-streaming requests
|
|
29
|
+
*
|
|
30
|
+
* The runtime tuple backs the request-time guard in `executeApiRequest`:
|
|
31
|
+
* the type-level union is erased in the bundled JS user APIs run as, so
|
|
32
|
+
* values outside it must also be rejected at runtime.
|
|
33
|
+
*/
|
|
34
|
+
export const REST_API_RESPONSE_TYPES = ["json", "text"] as const;
|
|
35
|
+
|
|
36
|
+
export type RestApiResponseType = (typeof REST_API_RESPONSE_TYPES)[number];
|
|
37
|
+
|
|
12
38
|
/**
|
|
13
39
|
* Options for making a generic REST API request.
|
|
14
40
|
*/
|
|
@@ -38,6 +64,15 @@ export interface ApiRequestOptions<TBody = unknown> {
|
|
|
38
64
|
* Optional HTTP headers
|
|
39
65
|
*/
|
|
40
66
|
headers?: Record<string, string>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* How the response body should be decoded. Defaults to `"json"`.
|
|
70
|
+
*
|
|
71
|
+
* Use `"text"` for endpoints that return non-JSON payloads such as
|
|
72
|
+
* XML. When requesting a text response, omit the response schema (or
|
|
73
|
+
* use one matching the decoded string, e.g. `z.string()`).
|
|
74
|
+
*/
|
|
75
|
+
responseType?: RestApiResponseType;
|
|
41
76
|
}
|
|
42
77
|
|
|
43
78
|
/**
|
|
@@ -46,7 +81,7 @@ export interface ApiRequestOptions<TBody = unknown> {
|
|
|
46
81
|
export interface ApiRequestSchema<TBody = unknown, TResponse = unknown> {
|
|
47
82
|
/**
|
|
48
83
|
* Optional Zod schema for request body validation.
|
|
49
|
-
*
|
|
84
|
+
* When omitted, `options.body` is sent without validation.
|
|
50
85
|
*/
|
|
51
86
|
body?: z.ZodSchema<TBody>;
|
|
52
87
|
|
|
@@ -60,14 +95,18 @@ export interface ApiRequestSchema<TBody = unknown, TResponse = unknown> {
|
|
|
60
95
|
/**
|
|
61
96
|
* Interface for integration clients that support generic API requests.
|
|
62
97
|
*
|
|
63
|
-
*
|
|
98
|
+
* Providing a response schema gives type-safe, validated results. Omitting
|
|
99
|
+
* it returns the raw response as `unknown`. For non-JSON payloads (e.g.
|
|
100
|
+
* `responseType: "text"` for XML), either omit the schema or supply one
|
|
101
|
+
* matching the decoded value (e.g. `z.string()` for text) — object schemas
|
|
102
|
+
* only make sense for JSON-shaped results.
|
|
64
103
|
*/
|
|
65
104
|
export interface SupportsApiRequest {
|
|
66
105
|
/**
|
|
67
106
|
* Execute a generic API request with type-safe validation.
|
|
68
107
|
*
|
|
69
108
|
* @param options - Request configuration including method, path, params, and body
|
|
70
|
-
* @param schema - Zod schemas for request body and response validation
|
|
109
|
+
* @param schema - Zod schemas for request body and response validation
|
|
71
110
|
* @param metadata - Optional trace metadata for observability (label, description)
|
|
72
111
|
* @returns Validated response data
|
|
73
112
|
*
|
|
@@ -103,4 +142,39 @@ export interface SupportsApiRequest {
|
|
|
103
142
|
},
|
|
104
143
|
metadata?: TraceMetadata,
|
|
105
144
|
): Promise<TResponse>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Execute a generic API request without response validation.
|
|
148
|
+
*
|
|
149
|
+
* This overload requires an explicit non-JSON `responseType` — JSON
|
|
150
|
+
* responses must always be consumed through the schema overload above,
|
|
151
|
+
* so "unvalidated JSON" is unrepresentable. The decoded non-JSON
|
|
152
|
+
* response is returned as-is, typed `unknown`.
|
|
153
|
+
*
|
|
154
|
+
* Note: request-body validation is opt-in — it runs only when
|
|
155
|
+
* `schema.body` is provided. Omitting `schema` sends `options.body`
|
|
156
|
+
* without validation.
|
|
157
|
+
*
|
|
158
|
+
* @param options - Request configuration; `responseType` must be "text"
|
|
159
|
+
* @param schema - Optional Zod schema for request body validation
|
|
160
|
+
* @param metadata - Optional trace metadata for observability (label, description)
|
|
161
|
+
* @returns The raw response from the integration
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* const xml = await ctx.integrations.legacyApi.apiRequest({
|
|
166
|
+
* method: 'GET',
|
|
167
|
+
* path: '/report.xml',
|
|
168
|
+
* responseType: 'text',
|
|
169
|
+
* });
|
|
170
|
+
* // xml is the raw XML string
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
apiRequest<TBody>(
|
|
174
|
+
options: ApiRequestOptions<TBody> & {
|
|
175
|
+
responseType: Exclude<RestApiResponseType, "json">;
|
|
176
|
+
},
|
|
177
|
+
schema?: ApiRequestSchema<TBody, unknown>,
|
|
178
|
+
metadata?: TraceMetadata,
|
|
179
|
+
): Promise<unknown>;
|
|
106
180
|
}
|
|
@@ -838,6 +838,37 @@ describe("resolveIntegrationDocumentation", () => {
|
|
|
838
838
|
});
|
|
839
839
|
});
|
|
840
840
|
|
|
841
|
+
describe("restapiintegration responseType gating (real docs)", () => {
|
|
842
|
+
// These resolve the actual shipped docs, not a fixture: the overlay
|
|
843
|
+
// gating is the discoverability guard for the responseType feature
|
|
844
|
+
// (agents whose worker sdk-api predates it must never be told the
|
|
845
|
+
// option exists), so the real manifest content is the contract.
|
|
846
|
+
|
|
847
|
+
it("documents responseType for agents whose sdk-api supports it", async () => {
|
|
848
|
+
const docs = await resolveIntegrationDocumentation("restapiintegration", {
|
|
849
|
+
sdkVersion: "0.0.3",
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
expect(docs).toContain('responseType: "text"');
|
|
853
|
+
expect(docs).not.toContain("does not support the `responseType`");
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
it("notes non-support instead of documenting responseType on older agents", async () => {
|
|
857
|
+
const docs = await resolveIntegrationDocumentation("restapiintegration", {
|
|
858
|
+
sdkVersion: "0.0.2",
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
expect(docs).toContain("does not support the `responseType`");
|
|
862
|
+
expect(docs).not.toContain('responseType: "text"');
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it("stays silent about responseType when the agent reports no sdk-api version", async () => {
|
|
866
|
+
const docs = await resolveIntegrationDocumentation("restapiintegration");
|
|
867
|
+
|
|
868
|
+
expect(docs).not.toContain("responseType");
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
|
|
841
872
|
it("blocks overlay paths outside the plugin directory", async () => {
|
|
842
873
|
const integrationsDirectory = createPluginDocsFixture("dropbox", {
|
|
843
874
|
"README.md": "base",
|