@superblocksteam/sdk-api 0.0.7 → 0.0.9

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 (62) hide show
  1. package/dist/errors.d.ts +6 -6
  2. package/dist/errors.d.ts.map +1 -1
  3. package/dist/errors.js +23 -2
  4. package/dist/errors.js.map +1 -1
  5. package/dist/integrations/base/decode-worker-binary-response.d.ts +4 -0
  6. package/dist/integrations/base/decode-worker-binary-response.d.ts.map +1 -0
  7. package/dist/integrations/base/decode-worker-binary-response.js +49 -0
  8. package/dist/integrations/base/decode-worker-binary-response.js.map +1 -0
  9. package/dist/integrations/base/decode-worker-binary-response.test.d.ts +2 -0
  10. package/dist/integrations/base/decode-worker-binary-response.test.d.ts.map +1 -0
  11. package/dist/integrations/base/decode-worker-binary-response.test.js +81 -0
  12. package/dist/integrations/base/decode-worker-binary-response.test.js.map +1 -0
  13. package/dist/integrations/base/index.d.ts +1 -1
  14. package/dist/integrations/base/index.d.ts.map +1 -1
  15. package/dist/integrations/base/rest-api-client-base.d.ts +0 -27
  16. package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
  17. package/dist/integrations/base/rest-api-client-base.js +34 -37
  18. package/dist/integrations/base/rest-api-client-base.js.map +1 -1
  19. package/dist/integrations/base/rest-api-integration-client.d.ts +11 -17
  20. package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
  21. package/dist/integrations/base/rest-api-integration-client.js +18 -17
  22. package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
  23. package/dist/integrations/base/types.d.ts +72 -13
  24. package/dist/integrations/base/types.d.ts.map +1 -1
  25. package/dist/integrations/base/types.js +1 -4
  26. package/dist/integrations/base/types.js.map +1 -1
  27. package/dist/integrations/documentation-resolver.test.js +173 -1
  28. package/dist/integrations/documentation-resolver.test.js.map +1 -1
  29. package/dist/integrations/documentation.d.ts +1 -0
  30. package/dist/integrations/documentation.d.ts.map +1 -1
  31. package/dist/integrations/documentation.js +31 -8
  32. package/dist/integrations/documentation.js.map +1 -1
  33. package/dist/integrations/postgres/client.d.ts +0 -7
  34. package/dist/integrations/postgres/client.d.ts.map +1 -1
  35. package/dist/integrations/restapiintegration/client.test.d.ts +2 -0
  36. package/dist/integrations/restapiintegration/client.test.d.ts.map +1 -0
  37. package/dist/integrations/restapiintegration/client.test.js +313 -0
  38. package/dist/integrations/restapiintegration/client.test.js.map +1 -0
  39. package/dist/integrations/slack/client.test.js +26 -1
  40. package/dist/integrations/slack/client.test.js.map +1 -1
  41. package/dist/integrations/slack/types.d.ts +2 -2
  42. package/dist/integrations/slack/types.d.ts.map +1 -1
  43. package/package.json +6 -6
  44. package/src/errors.ts +33 -5
  45. package/src/integrations/base/decode-worker-binary-response.test.ts +107 -0
  46. package/src/integrations/base/decode-worker-binary-response.ts +62 -0
  47. package/src/integrations/base/index.ts +1 -0
  48. package/src/integrations/base/rest-api-client-base.ts +42 -46
  49. package/src/integrations/base/rest-api-integration-client.ts +51 -21
  50. package/src/integrations/base/types.ts +85 -15
  51. package/src/integrations/documentation-resolver.test.ts +197 -1
  52. package/src/integrations/documentation.ts +63 -11
  53. package/src/integrations/graphql/docs.manifest.json +6 -1
  54. package/src/integrations/graphql/overlays/dynamic-headers.md +34 -0
  55. package/src/integrations/postgres/client.ts +1 -1
  56. package/src/integrations/restapiintegration/client.test.ts +480 -0
  57. package/src/integrations/restapiintegration/docs.manifest.json +14 -1
  58. package/src/integrations/restapiintegration/overlays/response-types-binary.md +51 -0
  59. package/src/integrations/restapiintegration/overlays/response-types-unsupported.md +7 -0
  60. package/src/integrations/restapiintegration/overlays/response-types.md +26 -0
  61. package/src/integrations/slack/client.test.ts +36 -1
  62. package/src/integrations/slack/types.ts +2 -2
@@ -0,0 +1,107 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { RestApiValidationError } from "../../errors.js";
4
+ import { decodeWorkerBinaryResponse } from "./decode-worker-binary-response.js";
5
+
6
+ describe("decodeWorkerBinaryResponse", () => {
7
+ it("copies a Uint8Array into a plain Uint8Array with the same bytes", () => {
8
+ const source = new Uint8Array([1, 2, 255]);
9
+
10
+ const result = decodeWorkerBinaryResponse(source);
11
+
12
+ expect(result).toBeInstanceOf(Uint8Array);
13
+ expect(result.constructor).toBe(Uint8Array);
14
+ expect(Array.from(result)).toEqual([1, 2, 255]);
15
+ source[0] = 9;
16
+ expect(result[0]).toBe(1);
17
+ });
18
+
19
+ it("converts worker Buffer JSON into a plain Uint8Array", () => {
20
+ const result = decodeWorkerBinaryResponse({
21
+ type: "Buffer",
22
+ data: [37, 80, 68, 70],
23
+ });
24
+
25
+ expect(result.constructor).toBe(Uint8Array);
26
+ expect(Array.from(result)).toEqual([37, 80, 68, 70]);
27
+ });
28
+
29
+ it("converts empty Buffer JSON into a zero-length Uint8Array", () => {
30
+ const result = decodeWorkerBinaryResponse({ type: "Buffer", data: [] });
31
+
32
+ expect(result).toBeInstanceOf(Uint8Array);
33
+ expect(result.byteLength).toBe(0);
34
+ });
35
+
36
+ it("rejects a missing discriminant", () => {
37
+ expect(() => decodeWorkerBinaryResponse({ data: [1] })).toThrow(
38
+ RestApiValidationError,
39
+ );
40
+ });
41
+
42
+ it("rejects a non-Buffer discriminant", () => {
43
+ expect(() =>
44
+ decodeWorkerBinaryResponse({ type: "Array", data: [1] }),
45
+ ).toThrow(RestApiValidationError);
46
+ });
47
+
48
+ it("rejects a non-array data field", () => {
49
+ expect(() =>
50
+ decodeWorkerBinaryResponse({ type: "Buffer", data: "00ff" }),
51
+ ).toThrow(RestApiValidationError);
52
+ });
53
+
54
+ it("rejects a byte outside 0 through 255", () => {
55
+ expect(() =>
56
+ decodeWorkerBinaryResponse({ type: "Buffer", data: [256] }),
57
+ ).toThrow(RestApiValidationError);
58
+ });
59
+
60
+ it("rejects a negative byte", () => {
61
+ expect(() =>
62
+ decodeWorkerBinaryResponse({ type: "Buffer", data: [-1] }),
63
+ ).toThrow(RestApiValidationError);
64
+ });
65
+
66
+ it("reports the index of a non-integer byte", () => {
67
+ try {
68
+ decodeWorkerBinaryResponse({ type: "Buffer", data: [1, 1.5] });
69
+ } catch (error) {
70
+ expect(error).toBeInstanceOf(RestApiValidationError);
71
+ if (!(error instanceof RestApiValidationError)) {
72
+ throw error;
73
+ }
74
+ expect(error.details.zodError.issues).toEqual([
75
+ expect.objectContaining({ path: ["data", 1] }),
76
+ ]);
77
+ return;
78
+ }
79
+ throw new Error("Expected binary response validation to fail");
80
+ });
81
+
82
+ it("redacts malformed binary data from validation errors", () => {
83
+ const data = Array.from({ length: 4096 }, () => 0);
84
+ data[4095] = 256;
85
+
86
+ try {
87
+ decodeWorkerBinaryResponse({ type: "Buffer", data });
88
+ } catch (error) {
89
+ expect(error).toBeInstanceOf(RestApiValidationError);
90
+ if (!(error instanceof RestApiValidationError)) {
91
+ throw error;
92
+ }
93
+ expect(error.details.data).toEqual({
94
+ dataType: "binary",
95
+ redacted: true,
96
+ });
97
+ return;
98
+ }
99
+ throw new Error("Expected malformed binary response to fail");
100
+ });
101
+
102
+ it("rejects a string payload", () => {
103
+ expect(() => decodeWorkerBinaryResponse("not-binary")).toThrow(
104
+ RestApiValidationError,
105
+ );
106
+ });
107
+ });
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+
3
+ import {
4
+ REDACTED_BINARY_RESPONSE_DATA,
5
+ RestApiValidationError,
6
+ } from "../../errors.js";
7
+
8
+ export { REDACTED_BINARY_RESPONSE_DATA };
9
+
10
+ const workerBinaryResponseSchema = z.union([
11
+ z.instanceof(Uint8Array).transform((value) => new Uint8Array(value)),
12
+ z
13
+ .object({
14
+ data: z.unknown(),
15
+ type: z.literal("Buffer"),
16
+ })
17
+ .transform((value, context) => {
18
+ if (!Array.isArray(value.data)) {
19
+ context.addIssue({
20
+ code: z.ZodIssueCode.custom,
21
+ message: "data must be an array of bytes",
22
+ path: ["data"],
23
+ });
24
+ return z.NEVER;
25
+ }
26
+
27
+ const bytes = new Uint8Array(value.data.length);
28
+ for (let index = 0; index < value.data.length; index += 1) {
29
+ const item = value.data[index];
30
+ if (
31
+ typeof item !== "number" ||
32
+ !Number.isInteger(item) ||
33
+ item < 0 ||
34
+ item > 255
35
+ ) {
36
+ context.addIssue({
37
+ code: z.ZodIssueCode.custom,
38
+ message: "byte must be an integer from 0 through 255",
39
+ path: ["data", index],
40
+ });
41
+ return z.NEVER;
42
+ }
43
+ bytes[index] = item;
44
+ }
45
+
46
+ return bytes;
47
+ }),
48
+ ]);
49
+
50
+ export function decodeWorkerBinaryResponse(value: unknown): Uint8Array {
51
+ const result = workerBinaryResponseSchema.safeParse(value);
52
+ if (!result.success) {
53
+ throw new RestApiValidationError(
54
+ `Binary response is malformed: ${result.error.message}`,
55
+ {
56
+ data: REDACTED_BINARY_RESPONSE_DATA,
57
+ zodError: result.error,
58
+ },
59
+ );
60
+ }
61
+ return result.data;
62
+ }
@@ -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";
@@ -1,13 +1,3 @@
1
- /**
2
- * Abstract base for REST API Integration clients.
3
- *
4
- * Owns the shared infrastructure that every REST-API-backed integration
5
- * needs: config storage, parameter helpers, body validation, request
6
- * construction, and query execution. Subclasses add their own
7
- * `apiRequest` with whatever response-handling strategy they need
8
- * (e.g. direct Zod validation, discriminated-union wrapping).
9
- */
10
-
11
1
  import type { PartialMessage } from "@bufbuild/protobuf";
12
2
  import { z } from "zod";
13
3
 
@@ -17,17 +7,12 @@ import type { Plugin as RestApiIntegrationPlugin } from "@superblocksteam/types/
17
7
  import { RestApiValidationError } from "../../errors.js";
18
8
  import type { QueryExecutor, TraceMetadata } from "../registry.js";
19
9
  import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
10
+ import { decodeWorkerBinaryResponse } from "./decode-worker-binary-response.js";
11
+ import { REST_API_RESPONSE_TYPES } from "./types.js";
20
12
  import type { ApiRequestOptions } from "./types.js";
21
13
 
22
14
  export type RestApiRequest = PartialMessage<RestApiIntegrationPlugin>;
23
15
 
24
- /**
25
- * Shared base for all REST API Integration clients.
26
- *
27
- * Provides config fields, parameter helpers, body validation, request
28
- * building, and query execution. Does NOT define `apiRequest` — each
29
- * concrete subclass supplies its own return-type contract.
30
- */
31
16
  export abstract class RestApiClientBase implements IntegrationClientImpl {
32
17
  readonly name: string;
33
18
  readonly pluginId: string;
@@ -52,23 +37,11 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
52
37
  };
53
38
  }
54
39
 
55
- /**
56
- * Validate the request body, build the proto request, and execute it.
57
- *
58
- * Returns the raw (unvalidated) response from the orchestrator.
59
- * Subclasses call this, then apply their own response handling.
60
- *
61
- * @param options - HTTP method, path, body, params, headers
62
- * @param bodySchema - Optional Zod schema for body validation
63
- * @param metadata - Optional trace metadata for observability
64
- * @returns Raw response from the orchestrator
65
- */
66
40
  protected async executeApiRequest<TBody>(
67
41
  options: ApiRequestOptions<TBody>,
68
42
  bodySchema?: z.ZodSchema<TBody>,
69
43
  metadata?: TraceMetadata,
70
44
  ): Promise<unknown> {
71
- // Validate request body if both body and schema are present.
72
45
  if (options.body !== undefined && bodySchema) {
73
46
  const bodyParseResult = bodySchema.safeParse(options.body);
74
47
  if (!bodyParseResult.success) {
@@ -96,13 +69,27 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
96
69
  }
97
70
  }
98
71
 
72
+ const responseTypeResult = z
73
+ .enum(REST_API_RESPONSE_TYPES)
74
+ .safeParse(options.responseType ?? "json");
75
+ if (!responseTypeResult.success) {
76
+ throw new RestApiValidationError(
77
+ `Unsupported responseType ${JSON.stringify(options.responseType)} - expected one of: ${REST_API_RESPONSE_TYPES.join(", ")}`,
78
+ {
79
+ zodError: responseTypeResult.error,
80
+ data: options.responseType,
81
+ },
82
+ );
83
+ }
84
+ const responseType = responseTypeResult.data;
85
+
99
86
  const request: RestApiRequest = {
100
87
  openApiAction: "genericHttpRequest",
101
88
  httpMethod: options.method.toUpperCase(),
102
89
  urlPath: options.path,
103
90
  headers,
104
91
  params,
105
- responseType: "json",
92
+ responseType,
106
93
  };
107
94
 
108
95
  if (options.body !== undefined) {
@@ -110,25 +97,34 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
110
97
  request.bodyType = "jsonBody";
111
98
  }
112
99
 
113
- const result = await this.executeQuery(
114
- request as Record<string, unknown>,
115
- undefined,
116
- metadata,
117
- );
100
+ const result = await this.executeQuery({ ...request }, undefined, metadata);
118
101
 
119
102
  if (result === null || result === undefined) {
120
- const nonNullResult = z.object({}).safeParse(result);
121
- if (!nonNullResult.success) {
122
- throw new RestApiValidationError(
123
- `Integration query returned ${String(result)} — expected a JSON response object`,
124
- {
125
- zodError: nonNullResult.error,
126
- data: result,
127
- },
128
- );
129
- }
103
+ throw new RestApiValidationError(
104
+ `Integration query returned ${String(result)} for responseType "${responseType}" - expected a response value`,
105
+ {
106
+ zodError: new z.ZodError([
107
+ {
108
+ code: z.ZodIssueCode.custom,
109
+ message: "response value is required",
110
+ path: [],
111
+ },
112
+ ]),
113
+ data: result,
114
+ },
115
+ );
130
116
  }
131
117
 
132
- return result;
118
+ switch (responseType) {
119
+ case "binary":
120
+ return decodeWorkerBinaryResponse(result);
121
+ case "json":
122
+ case "text":
123
+ return result;
124
+ default: {
125
+ const _exhaustive: never = responseType;
126
+ return _exhaustive;
127
+ }
128
+ }
133
129
  }
134
130
  }
@@ -1,12 +1,4 @@
1
- /**
2
- * Generic REST API Integration client with Zod response validation.
3
- *
4
- * Extends RestApiClientBase with an apiRequest() that validates the
5
- * full response against a caller-supplied Zod schema and throws
6
- * RestApiValidationError on mismatch.
7
- */
8
-
9
- import type { z } from "zod";
1
+ import { z } from "zod";
10
2
 
11
3
  import { RestApiValidationError } from "../../errors.js";
12
4
  import type { TraceMetadata } from "../registry.js";
@@ -14,38 +6,76 @@ import { RestApiClientBase } from "./rest-api-client-base.js";
14
6
  import type {
15
7
  ApiRequestOptions,
16
8
  ApiRequestSchema,
9
+ RestApiResponseType,
17
10
  SupportsApiRequest,
18
11
  } from "./types.js";
19
12
 
20
- /**
21
- * Base implementation for REST API Integration clients.
22
- *
23
- * All OpenAPI-based integration clients (except those with
24
- * integration-specific response handling) extend this class to
25
- * inherit the generic apiRequest() method with runtime Zod validation.
26
- */
27
13
  export abstract class RestApiIntegrationClient
28
14
  extends RestApiClientBase
29
15
  implements SupportsApiRequest
30
16
  {
31
17
  async apiRequest<TBody, TResponse>(
32
- options: ApiRequestOptions<TBody>,
18
+ options: ApiRequestOptions<TBody> & {
19
+ responseType?: Exclude<RestApiResponseType, "binary">;
20
+ },
33
21
  schema: ApiRequestSchema<TBody, TResponse> & {
34
22
  response: z.ZodSchema<TResponse>;
35
23
  },
36
24
  metadata?: TraceMetadata,
37
- ): Promise<TResponse> {
38
- const result = await this.executeApiRequest(options, schema.body, metadata);
25
+ ): Promise<TResponse>;
26
+ async apiRequest<TBody>(
27
+ options: ApiRequestOptions<TBody> & {
28
+ responseType: "binary";
29
+ },
30
+ schema?: ApiRequestSchema<TBody, Uint8Array>,
31
+ metadata?: TraceMetadata,
32
+ ): Promise<Uint8Array>;
33
+ async apiRequest<TBody>(
34
+ options: ApiRequestOptions<TBody> & {
35
+ responseType: "text";
36
+ },
37
+ schema?: ApiRequestSchema<TBody, unknown>,
38
+ metadata?: TraceMetadata,
39
+ ): Promise<unknown>;
40
+ async apiRequest<TBody, TResponse>(
41
+ options: ApiRequestOptions<TBody>,
42
+ schema?: ApiRequestSchema<TBody, TResponse>,
43
+ metadata?: TraceMetadata,
44
+ ): Promise<TResponse | Uint8Array | unknown> {
45
+ if ((options.responseType ?? "json") === "json" && !schema?.response) {
46
+ throw new RestApiValidationError(
47
+ 'apiRequest() with responseType "json" requires a response schema. Provide one, or set responseType to "text" or "binary"',
48
+ {
49
+ zodError: new z.ZodError([
50
+ {
51
+ code: z.ZodIssueCode.custom,
52
+ message: "response schema is required for JSON responses",
53
+ path: ["response"],
54
+ },
55
+ ]),
56
+ data: undefined,
57
+ },
58
+ );
59
+ }
60
+
61
+ const result = await this.executeApiRequest(
62
+ options,
63
+ schema?.body,
64
+ metadata,
65
+ );
66
+
67
+ if (!schema?.response) {
68
+ return result;
69
+ }
39
70
 
40
- // Response schema is REQUIRED - always validate
41
71
  const responseParseResult = schema.response.safeParse(result);
42
72
 
43
73
  if (!responseParseResult.success) {
44
74
  throw new RestApiValidationError(
45
75
  `Response validation failed: ${responseParseResult.error.message}`,
46
76
  {
47
- zodError: responseParseResult.error,
48
77
  data: result,
78
+ zodError: responseParseResult.error,
49
79
  },
50
80
  );
51
81
  }
@@ -1,17 +1,13 @@
1
- /**
2
- * Shared types for REST API Integration (OpenAPI) based clients.
3
- */
4
-
5
1
  import type { z } from "zod";
6
2
 
7
3
  import type { TraceMetadata } from "../registry.js";
8
4
 
9
- // Re-export for backwards compatibility
10
5
  export type { TraceMetadata };
11
6
 
12
- /**
13
- * Options for making a generic REST API request.
14
- */
7
+ export const REST_API_RESPONSE_TYPES = ["binary", "json", "text"] as const;
8
+
9
+ export type RestApiResponseType = (typeof REST_API_RESPONSE_TYPES)[number];
10
+
15
11
  export interface ApiRequestOptions<TBody = unknown> {
16
12
  /**
17
13
  * HTTP method for the request.
@@ -38,15 +34,23 @@ export interface ApiRequestOptions<TBody = unknown> {
38
34
  * Optional HTTP headers
39
35
  */
40
36
  headers?: Record<string, string>;
37
+
38
+ /**
39
+ * How the response body should be decoded. Defaults to `"json"`.
40
+ *
41
+ * Use `"text"` for endpoints that return non-JSON payloads such as
42
+ * XML. Use `"binary"` for PDFs and other byte payloads. When
43
+ * requesting a text or binary response, omit the response schema (or
44
+ * use one matching the decoded value, e.g. `z.string()` or
45
+ * `z.instanceof(Uint8Array)`).
46
+ */
47
+ responseType?: RestApiResponseType;
41
48
  }
42
49
 
43
- /**
44
- * Schema configuration for API request validation.
45
- */
46
50
  export interface ApiRequestSchema<TBody = unknown, TResponse = unknown> {
47
51
  /**
48
52
  * Optional Zod schema for request body validation.
49
- * Required if body is provided in options.
53
+ * When omitted, `options.body` is sent without validation.
50
54
  */
51
55
  body?: z.ZodSchema<TBody>;
52
56
 
@@ -60,14 +64,17 @@ export interface ApiRequestSchema<TBody = unknown, TResponse = unknown> {
60
64
  /**
61
65
  * Interface for integration clients that support generic API requests.
62
66
  *
63
- * All API requests require response schema validation for type safety.
67
+ * Providing a response schema gives type-safe, validated results. Omitting
68
+ * it returns the decoded response: `Uint8Array` for `responseType: "binary"`,
69
+ * or `unknown` for `responseType: "text"`. JSON still requires a schema.
70
+ * Object schemas only make sense for JSON-shaped results.
64
71
  */
65
72
  export interface SupportsApiRequest {
66
73
  /**
67
74
  * Execute a generic API request with type-safe validation.
68
75
  *
69
76
  * @param options - Request configuration including method, path, params, and body
70
- * @param schema - Zod schemas for request body and response validation (response schema REQUIRED)
77
+ * @param schema - Zod schemas for request body and response validation
71
78
  * @param metadata - Optional trace metadata for observability (label, description)
72
79
  * @returns Validated response data
73
80
  *
@@ -97,10 +104,73 @@ export interface SupportsApiRequest {
97
104
  * ```
98
105
  */
99
106
  apiRequest<TBody, TResponse>(
100
- options: ApiRequestOptions<TBody>,
107
+ options: ApiRequestOptions<TBody> & {
108
+ responseType?: Exclude<RestApiResponseType, "binary">;
109
+ },
101
110
  schema: ApiRequestSchema<TBody, TResponse> & {
102
111
  response: z.ZodSchema<TResponse>;
103
112
  },
104
113
  metadata?: TraceMetadata,
105
114
  ): Promise<TResponse>;
115
+
116
+ /**
117
+ * Execute a generic API request that returns binary data.
118
+ *
119
+ * @param options - Request configuration; `responseType` must be "binary"
120
+ * @param schema - Optional Zod schemas for request body and decoded response validation
121
+ * @param metadata - Optional trace metadata for observability (label, description)
122
+ * @returns The decoded body as a `Uint8Array`
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * const pdf = await ctx.integrations.legacyApi.apiRequest({
127
+ * method: 'GET',
128
+ * path: '/file.pdf',
129
+ * responseType: 'binary',
130
+ * });
131
+ * // pdf is a Uint8Array
132
+ * ```
133
+ */
134
+ apiRequest<TBody>(
135
+ options: ApiRequestOptions<TBody> & {
136
+ responseType: "binary";
137
+ },
138
+ schema?: ApiRequestSchema<TBody, Uint8Array>,
139
+ metadata?: TraceMetadata,
140
+ ): Promise<Uint8Array>;
141
+
142
+ /**
143
+ * Execute a generic API request without response validation.
144
+ *
145
+ * This overload requires an explicit `responseType: "text"` - JSON
146
+ * responses must always be consumed through the schema overload above,
147
+ * so "unvalidated JSON" is unrepresentable. The decoded text
148
+ * response is returned as-is, typed `unknown`.
149
+ *
150
+ * Note: request-body validation is opt-in - it runs only when
151
+ * `schema.body` is provided. Omitting `schema` sends `options.body`
152
+ * without validation.
153
+ *
154
+ * @param options - Request configuration; `responseType` must be "text"
155
+ * @param schema - Optional Zod schema for request body validation
156
+ * @param metadata - Optional trace metadata for observability (label, description)
157
+ * @returns The raw response from the integration
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const xml = await ctx.integrations.legacyApi.apiRequest({
162
+ * method: 'GET',
163
+ * path: '/report.xml',
164
+ * responseType: 'text',
165
+ * });
166
+ * // xml is the raw XML string
167
+ * ```
168
+ */
169
+ apiRequest<TBody>(
170
+ options: ApiRequestOptions<TBody> & {
171
+ responseType: "text";
172
+ },
173
+ schema?: ApiRequestSchema<TBody, unknown>,
174
+ metadata?: TraceMetadata,
175
+ ): Promise<unknown>;
106
176
  }