@superblocksteam/sdk-api 2.0.157 → 2.0.158

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 (57) 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/rest-api-client-base.d.ts +0 -27
  14. package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
  15. package/dist/integrations/base/rest-api-client-base.js +23 -46
  16. package/dist/integrations/base/rest-api-client-base.js.map +1 -1
  17. package/dist/integrations/base/rest-api-integration-client.d.ts +7 -19
  18. package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
  19. package/dist/integrations/base/rest-api-integration-client.js +2 -29
  20. package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
  21. package/dist/integrations/base/types.d.ts +36 -43
  22. package/dist/integrations/base/types.d.ts.map +1 -1
  23. package/dist/integrations/base/types.js +1 -26
  24. package/dist/integrations/base/types.js.map +1 -1
  25. package/dist/integrations/documentation-resolver.test.js +11 -5
  26. package/dist/integrations/documentation-resolver.test.js.map +1 -1
  27. package/dist/integrations/restapiintegration/client.test.d.ts +0 -10
  28. package/dist/integrations/restapiintegration/client.test.d.ts.map +1 -1
  29. package/dist/integrations/restapiintegration/client.test.js +146 -69
  30. package/dist/integrations/restapiintegration/client.test.js.map +1 -1
  31. package/dist/integrations/slack/client.test.js +26 -1
  32. package/dist/integrations/slack/client.test.js.map +1 -1
  33. package/package.json +2 -2
  34. package/src/errors.ts +33 -5
  35. package/src/integrations/anthropic/README.md +7 -0
  36. package/src/integrations/base/decode-worker-binary-response.test.ts +107 -0
  37. package/src/integrations/base/decode-worker-binary-response.ts +62 -0
  38. package/src/integrations/base/rest-api-client-base.ts +28 -56
  39. package/src/integrations/base/rest-api-integration-client.ts +14 -33
  40. package/src/integrations/base/types.ts +41 -45
  41. package/src/integrations/bigquery/README.md +1 -0
  42. package/src/integrations/box/README.md +3 -0
  43. package/src/integrations/cohere/README.md +7 -0
  44. package/src/integrations/documentation-resolver.test.ts +13 -6
  45. package/src/integrations/fireworks/README.md +7 -0
  46. package/src/integrations/gemini/README.md +8 -0
  47. package/src/integrations/groq/README.md +7 -0
  48. package/src/integrations/mistral/README.md +7 -0
  49. package/src/integrations/openai_v2/README.md +7 -0
  50. package/src/integrations/perplexity/README.md +7 -0
  51. package/src/integrations/restapiintegration/client.test.ts +207 -86
  52. package/src/integrations/restapiintegration/docs.manifest.json +5 -1
  53. package/src/integrations/restapiintegration/overlays/response-types-binary.md +51 -0
  54. package/src/integrations/s3/README.md +1 -0
  55. package/src/integrations/slack/client.test.ts +36 -1
  56. package/src/integrations/snowflakecortex/README.md +8 -0
  57. package/src/integrations/stabilityai/README.md +7 -0
@@ -257,6 +257,13 @@ All methods accept an optional `metadata` parameter as the last argument for dia
257
257
 
258
258
  ## Common Pitfalls
259
259
 
260
+ ### Streaming Is Not Supported
261
+
262
+ `apiRequest()` does not support streaming or Server-Sent Events. Do not set
263
+ `stream: true` — streaming responses fail schema validation. Every call
264
+ returns the complete response; if a UI needs real-time token streaming,
265
+ handle it at the frontend layer, not through the SDK.
266
+
260
267
  ### No Specialized Methods
261
268
 
262
269
  The OpenAI client only provides `apiRequest()`. There are no other specialized methods:
@@ -189,6 +189,13 @@ All methods accept an optional `metadata` parameter as the last argument for dia
189
189
 
190
190
  ## Common Pitfalls
191
191
 
192
+ ### Streaming Is Not Supported
193
+
194
+ `apiRequest()` does not support streaming or Server-Sent Events. Do not set
195
+ `stream: true` — streaming responses fail schema validation. Every call
196
+ returns the complete response; if a UI needs real-time token streaming,
197
+ handle it at the frontend layer, not through the SDK.
198
+
192
199
  ### No Specialized Methods
193
200
 
194
201
  ```typescript
@@ -1,37 +1,13 @@
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
-
12
- import { describe, it, expect, vi } from "vitest";
1
+ import { describe, expect, expectTypeOf, it, vi } from "vitest";
13
2
  import { z } from "zod";
14
3
 
15
- import { ActionResponseType } from "@superblocksteam/shared";
4
+ import type { ActionResponseType } from "@superblocksteam/shared";
16
5
 
17
6
  import { RestApiValidationError } from "../../errors.js";
18
- import type { RestApiResponseType } from "../base/types.js";
7
+ import { REST_API_RESPONSE_TYPES } from "../base/types.js";
19
8
  import type { IntegrationConfig } from "../types.js";
20
9
  import { RestApiIntegrationPluginClientImpl } from "./client.js";
21
10
 
22
- // ── Wire-contract assertions (verified by `pnpm typecheck`) ────────
23
- //
24
- // The responseType strings are sent to the orchestrator as-is; the
25
- // accepted values are defined by ActionResponseType. This assertion
26
- // fails compilation if the SDK union drifts outside the wire values.
27
- //
28
- // The SDK union is a deliberate subset of the wire values: "auto" and
29
- // "binary" are implemented by the orchestrator but not exposed (see the
30
- // RestApiResponseType doc for why), and "raw" is streaming-only.
31
- const _sdkValuesAreValidWireValues: Exclude<`${ActionResponseType}`, "raw">[] =
32
- [] as RestApiResponseType[];
33
- void _sdkValuesAreValidWireValues;
34
-
35
11
  const TEST_CONFIG: IntegrationConfig = {
36
12
  id: "restapi-test-id",
37
13
  name: "Test REST API",
@@ -40,6 +16,11 @@ const TEST_CONFIG: IntegrationConfig = {
40
16
  };
41
17
 
42
18
  const XML_RESPONSE = `<?xml version="1.0"?><note><body>hi</body></note>`;
19
+ const PDF_BYTES = [0x25, 0x50, 0x44, 0x46];
20
+
21
+ function workerBufferPayload(data: unknown): unknown {
22
+ return { type: "Buffer", data };
23
+ }
43
24
 
44
25
  function createClient(mockResult: unknown) {
45
26
  const executeQuery = vi.fn().mockResolvedValue(mockResult);
@@ -50,9 +31,14 @@ function createClient(mockResult: unknown) {
50
31
  return { client, executeQuery };
51
32
  }
52
33
 
53
- describe("RestApiIntegrationPluginClientImpl", () => {
54
- // ── Default JSON behavior (must not regress) ────────────────────
34
+ function callApiRequest(
35
+ client: RestApiIntegrationPluginClientImpl,
36
+ ...args: unknown[]
37
+ ): Promise<unknown> {
38
+ return Reflect.apply(client.apiRequest, client, args);
39
+ }
55
40
 
41
+ describe("RestApiIntegrationPluginClientImpl", () => {
56
42
  describe("default JSON behavior", () => {
57
43
  it("sends responseType json when the option is omitted", async () => {
58
44
  const { client, executeQuery } = createClient({ id: "1" });
@@ -81,19 +67,26 @@ describe("RestApiIntegrationPluginClientImpl", () => {
81
67
  });
82
68
 
83
69
  it("throws RestApiValidationError when the response does not match the schema", async () => {
84
- const { client } = createClient(XML_RESPONSE);
70
+ const payload = XML_RESPONSE;
71
+ const { client } = createClient(payload);
85
72
 
86
- await expect(
87
- client.apiRequest(
73
+ try {
74
+ await client.apiRequest(
88
75
  { method: "GET", path: "/users" },
89
76
  { response: z.object({ id: z.string() }) },
90
- ),
91
- ).rejects.toThrow(RestApiValidationError);
77
+ );
78
+ } catch (error) {
79
+ expect(error).toBeInstanceOf(RestApiValidationError);
80
+ if (!(error instanceof RestApiValidationError)) {
81
+ throw error;
82
+ }
83
+ expect(error.details.data).toBe(payload);
84
+ return;
85
+ }
86
+ throw new Error("Expected JSON response validation to fail");
92
87
  });
93
88
  });
94
89
 
95
- // ── responseType passthrough ────────────────────────────────────
96
-
97
90
  describe("responseType passthrough", () => {
98
91
  it("forwards responseType text to the orchestrator request", async () => {
99
92
  const { client, executeQuery } = createClient(XML_RESPONSE);
@@ -152,8 +145,6 @@ describe("RestApiIntegrationPluginClientImpl", () => {
152
145
  });
153
146
 
154
147
  it("returns an empty string unchanged for responseType text", async () => {
155
- // An empty text body legitimately decodes to "" — the null/undefined
156
- // guard must not reject it.
157
148
  const { client } = createClient("");
158
149
 
159
150
  const result = await client.apiRequest({
@@ -166,10 +157,6 @@ describe("RestApiIntegrationPluginClientImpl", () => {
166
157
  });
167
158
 
168
159
  it("rejects null and undefined results for every response type", async () => {
169
- // Neither value is a legitimate decode result for the exposed
170
- // response types: JSON parses to a value the schema sees, and an
171
- // empty text body decodes to "". Either means a broken execution
172
- // contract, so both throw.
173
160
  for (const badResult of [null, undefined]) {
174
161
  const { client: textClient } = createClient(badResult);
175
162
  await expect(
@@ -180,8 +167,15 @@ describe("RestApiIntegrationPluginClientImpl", () => {
180
167
  }),
181
168
  ).rejects.toThrow(RestApiValidationError);
182
169
 
183
- // JSON mode always goes through the schema overload; the bad
184
- // result is rejected before validation runs.
170
+ const { client: binaryClient } = createClient(badResult);
171
+ await expect(
172
+ binaryClient.apiRequest({
173
+ method: "GET",
174
+ path: "/broken",
175
+ responseType: "binary",
176
+ }),
177
+ ).rejects.toThrow(RestApiValidationError);
178
+
185
179
  const { client: jsonClient } = createClient(badResult);
186
180
  await expect(
187
181
  jsonClient.apiRequest(
@@ -193,14 +187,109 @@ describe("RestApiIntegrationPluginClientImpl", () => {
193
187
  });
194
188
  });
195
189
 
196
- // ── Request-body validation via the schema-less overload ────────
190
+ describe("responseType binary", () => {
191
+ it("forwards responseType binary and returns decoded bytes", async () => {
192
+ const { client, executeQuery } = createClient(
193
+ workerBufferPayload(PDF_BYTES),
194
+ );
195
+
196
+ const result = await client.apiRequest({
197
+ method: "GET",
198
+ path: "/file.pdf",
199
+ responseType: "binary",
200
+ });
201
+
202
+ expect(executeQuery).toHaveBeenCalledWith(
203
+ expect.objectContaining({ responseType: "binary" }),
204
+ undefined,
205
+ undefined,
206
+ );
207
+ expect(result).toBeInstanceOf(Uint8Array);
208
+ expect(result.constructor).toBe(Uint8Array);
209
+ expect(Array.from(result)).toEqual(PDF_BYTES);
210
+ });
211
+
212
+ it("rejects malformed binary data before applying a response schema", async () => {
213
+ const malformed = workerBufferPayload([256]);
214
+ const { client, executeQuery } = createClient(malformed);
215
+ const workerShapeSchema = z.object({
216
+ type: z.literal("Buffer"),
217
+ data: z.array(z.number()),
218
+ });
219
+
220
+ await expect(
221
+ callApiRequest(
222
+ client,
223
+ { method: "GET", path: "/file.pdf", responseType: "binary" },
224
+ { response: workerShapeSchema },
225
+ ),
226
+ ).rejects.toThrow(RestApiValidationError);
227
+ expect(executeQuery).toHaveBeenCalled();
228
+ });
229
+
230
+ it("accepts response schemas that transform bytes to bytes", async () => {
231
+ const { client } = createClient(workerBufferPayload(PDF_BYTES));
232
+
233
+ const result = await client.apiRequest(
234
+ { method: "GET", path: "/file.pdf", responseType: "binary" },
235
+ {
236
+ response: z
237
+ .instanceof(Uint8Array)
238
+ .refine((bytes) => bytes[0] === 0x25)
239
+ .transform((bytes) => bytes.slice(1)),
240
+ },
241
+ );
242
+
243
+ expect(Array.from(result)).toEqual(PDF_BYTES.slice(1));
244
+ });
245
+
246
+ it("throws RestApiValidationError when the schema rejects normalized bytes", async () => {
247
+ const { client } = createClient(workerBufferPayload(PDF_BYTES));
248
+
249
+ await expect(
250
+ client.apiRequest(
251
+ { method: "GET", path: "/file.pdf", responseType: "binary" },
252
+ {
253
+ response: z
254
+ .instanceof(Uint8Array)
255
+ .refine((bytes) => bytes.byteLength > 64),
256
+ },
257
+ ),
258
+ ).rejects.toThrow(RestApiValidationError);
259
+ });
260
+
261
+ it("redacts binary data from response validation errors", async () => {
262
+ const bytes = Array.from({ length: 4096 }, () => 0);
263
+ const { client } = createClient(workerBufferPayload(bytes));
264
+
265
+ try {
266
+ await client.apiRequest(
267
+ { method: "GET", path: "/file.pdf", responseType: "binary" },
268
+ {
269
+ response: z
270
+ .instanceof(Uint8Array)
271
+ .refine((value) => value.byteLength < 1024),
272
+ },
273
+ );
274
+ } catch (error) {
275
+ expect(error).toBeInstanceOf(RestApiValidationError);
276
+ if (!(error instanceof RestApiValidationError)) {
277
+ throw error;
278
+ }
279
+ expect(error.details.data).toEqual({
280
+ dataType: "binary",
281
+ redacted: true,
282
+ });
283
+ return;
284
+ }
285
+ throw new Error("Expected binary response validation to fail");
286
+ });
287
+ });
197
288
 
198
289
  describe("request body validation", () => {
199
290
  it("throws RestApiValidationError for an invalid body with a body-only schema", async () => {
200
291
  const { client, executeQuery } = createClient({ ok: true });
201
292
 
202
- // Runtime-invalid bodies reach this path via dynamic data (user
203
- // input, parsed JSON) that the compiler cannot check.
204
293
  const dynamicBody: unknown = JSON.parse('{"amount":"not-a-number"}');
205
294
 
206
295
  await expect(
@@ -242,23 +331,12 @@ describe("RestApiIntegrationPluginClientImpl", () => {
242
331
  });
243
332
  });
244
333
 
245
- // ── Runtime contract enforcement ─────────────────────────────────
246
- //
247
- // The overloads make these calls unrepresentable in TypeScript, but
248
- // user API code executes as esbuild-bundled JS with no type
249
- // enforcement — the same contract must hold at runtime.
250
-
251
334
  describe("runtime contract enforcement", () => {
252
- type UncheckedApiRequest = (...args: unknown[]) => Promise<unknown>;
253
-
254
335
  it("rejects a schema-less call in default JSON mode without issuing the request", async () => {
255
336
  const { client, executeQuery } = createClient({ ok: true });
256
337
 
257
338
  await expect(
258
- (client.apiRequest as UncheckedApiRequest)({
259
- method: "GET",
260
- path: "/users",
261
- }),
339
+ callApiRequest(client, { method: "GET", path: "/users" }),
262
340
  ).rejects.toThrow(RestApiValidationError);
263
341
  expect(executeQuery).not.toHaveBeenCalled();
264
342
  });
@@ -267,7 +345,8 @@ describe("RestApiIntegrationPluginClientImpl", () => {
267
345
  const { client, executeQuery } = createClient({ ok: true });
268
346
 
269
347
  await expect(
270
- (client.apiRequest as UncheckedApiRequest)(
348
+ callApiRequest(
349
+ client,
271
350
  {
272
351
  method: "POST",
273
352
  path: "/orders",
@@ -280,27 +359,32 @@ describe("RestApiIntegrationPluginClientImpl", () => {
280
359
  expect(executeQuery).not.toHaveBeenCalled();
281
360
  });
282
361
 
283
- it("rejects an unexposed responseType value without issuing the request", async () => {
284
- const { client, executeQuery } = createClient({ ok: true });
362
+ it("rejects auto, which can yield unvalidated JSON, and raw, which is streaming-only", async () => {
363
+ for (const responseType of ["auto", "raw"]) {
364
+ const { client, executeQuery } = createClient({ ok: true });
285
365
 
286
- await expect(
287
- (client.apiRequest as UncheckedApiRequest)({
288
- method: "GET",
289
- path: "/report",
290
- responseType: "auto",
291
- }),
292
- ).rejects.toThrow(RestApiValidationError);
293
- expect(executeQuery).not.toHaveBeenCalled();
366
+ await expect(
367
+ callApiRequest(client, {
368
+ method: "GET",
369
+ path: "/report",
370
+ responseType,
371
+ }),
372
+ ).rejects.toThrow(RestApiValidationError);
373
+ expect(executeQuery).not.toHaveBeenCalled();
374
+ }
294
375
  });
295
376
  });
296
377
 
297
- // ── Compile-time overload contracts (verified by `pnpm typecheck`) ──
298
-
299
378
  describe("overload resolution", () => {
379
+ it("keeps public response types within the worker contract", () => {
380
+ expectTypeOf<(typeof REST_API_RESPONSE_TYPES)[number]>().toMatchTypeOf<
381
+ Exclude<`${ActionResponseType}`, "raw">
382
+ >();
383
+ });
384
+
300
385
  it("keeps typed results for schema callers and unknown for schema-less calls", async () => {
301
386
  const { client } = createClient({ id: "1" });
302
387
 
303
- // With a response schema: resolves to the typed overload.
304
388
  const typed: Promise<{ id: string }> = client.apiRequest(
305
389
  { method: "GET", path: "/users" },
306
390
  { response: z.object({ id: z.string() }) },
@@ -309,7 +393,6 @@ describe("RestApiIntegrationPluginClientImpl", () => {
309
393
 
310
394
  const { client: rawClient } = createClient(XML_RESPONSE);
311
395
 
312
- // Without a schema: resolves to the raw overload returning unknown.
313
396
  const raw: Promise<unknown> = rawClient.apiRequest({
314
397
  method: "GET",
315
398
  path: "/report.xml",
@@ -325,20 +408,58 @@ describe("RestApiIntegrationPluginClientImpl", () => {
325
408
  });
326
409
  await wrong;
327
410
 
328
- // The remaining cases are rejected twice over: at compile time by
329
- // the overloads (@ts-expect-error) and at runtime by the contract
330
- // guards, since bundled user code carries no types.
331
-
332
- // @ts-expect-error unvalidated JSON is unrepresentable — schema-less
333
- // calls must opt into a non-JSON responseType
411
+ // @ts-expect-error unvalidated JSON is unrepresentable; schema-less calls must opt into a non-JSON responseType
334
412
  const unvalidatedJson = rawClient.apiRequest({
335
413
  method: "GET",
336
414
  path: "/users",
337
415
  });
338
416
  await expect(unvalidatedJson).rejects.toThrow(RestApiValidationError);
417
+ });
418
+
419
+ it("types schema-less binary responses as Uint8Array", async () => {
420
+ const { client: binaryClient } = createClient(
421
+ workerBufferPayload(PDF_BYTES),
422
+ );
423
+
424
+ const binary: Promise<Uint8Array> = binaryClient.apiRequest({
425
+ method: "GET",
426
+ path: "/file.pdf",
427
+ responseType: "binary",
428
+ });
429
+ expectTypeOf(binary).toEqualTypeOf<Promise<Uint8Array>>();
430
+ expect(Array.from(await binary)).toEqual(PDF_BYTES);
431
+
432
+ // @ts-expect-error schema-less binary apiRequest returns Uint8Array
433
+ const wrongBinary: Promise<string> = binaryClient.apiRequest({
434
+ method: "GET",
435
+ path: "/file.pdf",
436
+ responseType: "binary",
437
+ });
438
+ await wrongBinary;
439
+ });
440
+
441
+ it("rejects binary response schemas that transform bytes to another type", () => {
442
+ const { client: binaryClient } = createClient(
443
+ workerBufferPayload(PDF_BYTES),
444
+ );
339
445
 
340
- // "auto" and "binary" are valid wire values but deliberately not
341
- // exposed in the SDK union (see RestApiResponseType).
446
+ // @ts-expect-error binary response schemas must output Uint8Array
447
+ binaryClient.apiRequest(
448
+ {
449
+ method: "GET",
450
+ path: "/file.pdf",
451
+ responseType: "binary",
452
+ },
453
+ {
454
+ response: z
455
+ .instanceof(Uint8Array)
456
+ .transform((bytes) => bytes.byteLength),
457
+ },
458
+ );
459
+ });
460
+
461
+ it("rejects response types outside the public SDK contract", async () => {
462
+ const { client: rawClient } = createClient(XML_RESPONSE);
342
463
  const autoCall = rawClient.apiRequest({
343
464
  method: "GET",
344
465
  path: "/report.xml",
@@ -347,13 +468,13 @@ describe("RestApiIntegrationPluginClientImpl", () => {
347
468
  });
348
469
  await expect(autoCall).rejects.toThrow(RestApiValidationError);
349
470
 
350
- const binaryCall = rawClient.apiRequest({
471
+ const rawCall = rawClient.apiRequest({
351
472
  method: "GET",
352
473
  path: "/report.xml",
353
- // @ts-expect-error "binary" is not an exposed responseType
354
- responseType: "binary",
474
+ // @ts-expect-error "raw" is not an exposed responseType
475
+ responseType: "raw",
355
476
  });
356
- await expect(binaryCall).rejects.toThrow(RestApiValidationError);
477
+ await expect(rawCall).rejects.toThrow(RestApiValidationError);
357
478
  });
358
479
  });
359
480
  });
@@ -4,7 +4,11 @@
4
4
  "overlays": [
5
5
  {
6
6
  "file": "overlays/response-types.md",
7
- "sdkVersionRange": ">=0.0.3"
7
+ "sdkVersionRange": ">=0.0.3 <0.0.4"
8
+ },
9
+ {
10
+ "file": "overlays/response-types-binary.md",
11
+ "sdkVersionRange": ">=0.0.4"
8
12
  },
9
13
  {
10
14
  "file": "overlays/response-types-unsupported.md",
@@ -0,0 +1,51 @@
1
+ ## Non-JSON Responses
2
+
3
+ `apiRequest()` decodes responses as JSON by default. For endpoints that return non-JSON payloads, pass `responseType`:
4
+
5
+ - `"binary"` for PDFs and other byte payloads. The result is a `Uint8Array`
6
+ - `"text"` for XML, CSV, HTML, and other decoded strings
7
+
8
+ With `responseType: "text"` or `responseType: "binary"` the response schema may be omitted. Text returns the raw string typed `unknown`. Binary returns a `Uint8Array`:
9
+
10
+ ```typescript
11
+ const xml = await ctx.integrations.legacyApi.apiRequest({
12
+ method: "GET",
13
+ path: "/report.xml",
14
+ responseType: "text",
15
+ });
16
+ // xml is the raw XML string (typed unknown)
17
+
18
+ const pdf = await ctx.integrations.legacyApi.apiRequest({
19
+ method: "GET",
20
+ path: "/file.pdf",
21
+ responseType: "binary",
22
+ });
23
+ // pdf is a Uint8Array
24
+ ```
25
+
26
+ To validate after decoding, pass a schema that matches the decoded value:
27
+
28
+ ```typescript
29
+ const xml = await ctx.integrations.legacyApi.apiRequest(
30
+ { method: "GET", path: "/report.xml", responseType: "text" },
31
+ { response: z.string() },
32
+ );
33
+ // xml is typed string
34
+
35
+ const pdf = await ctx.integrations.legacyApi.apiRequest(
36
+ { method: "GET", path: "/file.pdf", responseType: "binary" },
37
+ { response: z.instanceof(Uint8Array) },
38
+ );
39
+ // pdf is typed Uint8Array
40
+ ```
41
+
42
+ Convert binary data to a JSON-safe representation before returning it from an SDK API:
43
+
44
+ ```typescript
45
+ return {
46
+ contentBase64: Buffer.from(pdf).toString("base64"),
47
+ filename: "file.pdf",
48
+ };
49
+ ```
50
+
51
+ JSON responses (the default) always require a response schema. Omitting the schema is only allowed together with an explicit `responseType: "text"` or `responseType: "binary"`, so unvalidated JSON is not representable.
@@ -25,6 +25,7 @@ import { api, z, s3 } from "@superblocksteam/sdk-api";
25
25
  const PROD_S3 = "a1b2c3d4-5678-90ab-cdef-s300000001";
26
26
 
27
27
  export default api({
28
+ name: "ListBucketObjects",
28
29
  integrations: {
29
30
  storage: s3(PROD_S3),
30
31
  },
@@ -12,6 +12,7 @@ import { describe, it, expect, vi } from "vitest";
12
12
  import { z } from "zod";
13
13
 
14
14
  import { RestApiValidationError } from "../../errors.js";
15
+ import { REDACTED_BINARY_RESPONSE_DATA } from "../base/decode-worker-binary-response.js";
15
16
  import type { IntegrationConfig } from "../types.js";
16
17
  import { SlackClientImpl } from "./client.js";
17
18
  import type { SlackErrorResponse } from "./types.js";
@@ -508,7 +509,41 @@ describe("SlackClientImpl", () => {
508
509
  });
509
510
  });
510
511
 
511
- // ── Type narrowing (compile-time verification) ─────────────────
512
+ describe("responseType binary", () => {
513
+ const pdfBytes = [0x25, 0x50, 0x44, 0x46];
514
+ const workerPayload = { type: "Buffer", data: pdfBytes };
515
+
516
+ it("decodes worker Buffer JSON and redacts it from validation errors", async () => {
517
+ const { client, executeQuery } = createClient(workerPayload);
518
+
519
+ try {
520
+ await client.apiRequest(
521
+ {
522
+ method: "GET",
523
+ path: "/files.download",
524
+ responseType: "binary",
525
+ },
526
+ { response: ChannelsSchema },
527
+ );
528
+ } catch (error) {
529
+ expect(error).toBeInstanceOf(RestApiValidationError);
530
+ if (!(error instanceof RestApiValidationError)) {
531
+ throw error;
532
+ }
533
+ expect(executeQuery).toHaveBeenCalledWith(
534
+ expect.objectContaining({ responseType: "binary" }),
535
+ undefined,
536
+ undefined,
537
+ );
538
+ expect(error.details.data).toEqual(REDACTED_BINARY_RESPONSE_DATA);
539
+ expect(error.details.data).not.toEqual(workerPayload);
540
+ return;
541
+ }
542
+ throw new Error(
543
+ "Expected Slack binary apiRequest to fail envelope validation",
544
+ );
545
+ });
546
+ });
512
547
 
513
548
  describe("type narrowing", () => {
514
549
  it("narrows to error response fields when ok is false", async () => {
@@ -108,6 +108,14 @@ All methods accept an optional `metadata` parameter as the last argument for dia
108
108
 
109
109
  ## Common Pitfalls
110
110
 
111
+ ### Streaming Is Not Supported
112
+
113
+ `apiRequest()` does not support streaming or Server-Sent Events. Always set
114
+ `stream: false` in Cortex request bodies (as the examples do) — a streaming
115
+ response fails schema validation. Every call returns the complete response;
116
+ if a UI needs real-time token streaming, handle it at the frontend layer,
117
+ not through the SDK.
118
+
111
119
  ### No Specialized Methods
112
120
 
113
121
  The Snowflake Cortex client only provides `apiRequest()`. There are no other specialized methods:
@@ -234,6 +234,13 @@ All methods accept an optional `metadata` parameter as the last argument for dia
234
234
 
235
235
  ## Common Pitfalls
236
236
 
237
+ ### Streaming Is Not Supported
238
+
239
+ `apiRequest()` does not support streaming or Server-Sent Events. Do not set
240
+ `stream: true` — streaming responses fail schema validation. Every call
241
+ returns the complete response; if a UI needs real-time token streaming,
242
+ handle it at the frontend layer, not through the SDK.
243
+
237
244
  ### No Specialized Methods
238
245
 
239
246
  ```typescript