@superblocksteam/sdk-api 2.0.142-next.0 → 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.
Files changed (30) hide show
  1. package/dist/integrations/base/index.d.ts +1 -1
  2. package/dist/integrations/base/index.d.ts.map +1 -1
  3. package/dist/integrations/base/rest-api-client-base.d.ts +1 -1
  4. package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
  5. package/dist/integrations/base/rest-api-client-base.js +23 -3
  6. package/dist/integrations/base/rest-api-client-base.js.map +1 -1
  7. package/dist/integrations/base/rest-api-integration-client.d.ts +9 -3
  8. package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
  9. package/dist/integrations/base/rest-api-integration-client.js +31 -3
  10. package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
  11. package/dist/integrations/base/types.d.ts +69 -3
  12. package/dist/integrations/base/types.d.ts.map +1 -1
  13. package/dist/integrations/base/types.js +23 -1
  14. package/dist/integrations/base/types.js.map +1 -1
  15. package/dist/integrations/documentation-resolver.test.js +24 -0
  16. package/dist/integrations/documentation-resolver.test.js.map +1 -1
  17. package/dist/integrations/restapiintegration/client.test.d.ts +12 -0
  18. package/dist/integrations/restapiintegration/client.test.d.ts.map +1 -0
  19. package/dist/integrations/restapiintegration/client.test.js +236 -0
  20. package/dist/integrations/restapiintegration/client.test.js.map +1 -0
  21. package/package.json +3 -2
  22. package/src/integrations/base/index.ts +1 -0
  23. package/src/integrations/base/rest-api-client-base.ts +27 -3
  24. package/src/integrations/base/rest-api-integration-client.ts +54 -5
  25. package/src/integrations/base/types.ts +77 -3
  26. package/src/integrations/documentation-resolver.test.ts +31 -0
  27. package/src/integrations/restapiintegration/client.test.ts +359 -0
  28. package/src/integrations/restapiintegration/docs.manifest.json +10 -1
  29. package/src/integrations/restapiintegration/overlays/response-types-unsupported.md +5 -0
  30. 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.0",
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",
@@ -9,5 +9,6 @@ export { GraphQLIntegrationClient } from "./graphql-integration-client.js";
9
9
  export type {
10
10
  ApiRequestOptions,
11
11
  ApiRequestSchema,
12
+ RestApiResponseType,
12
13
  SupportsApiRequest,
13
14
  } from "./types.js";
@@ -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 - HTTP method, path, body, params, headers
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: "json",
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 JSON response object`,
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 type { z } from "zod";
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
- const result = await this.executeApiRequest(options, schema.body, metadata);
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
- * Required if body is provided in options.
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
- * All API requests require response schema validation for type safety.
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 (response schema REQUIRED)
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",