@superblocksteam/sdk-api 2.0.156-next.3 → 2.0.156
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/errors.d.ts +6 -6
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -23
- package/dist/errors.js.map +1 -1
- package/dist/integrations/base/rest-api-client-base.d.ts +27 -0
- package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
- package/dist/integrations/base/rest-api-client-base.js +46 -23
- package/dist/integrations/base/rest-api-client-base.js.map +1 -1
- package/dist/integrations/base/rest-api-integration-client.d.ts +19 -7
- package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
- package/dist/integrations/base/rest-api-integration-client.js +29 -2
- package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
- package/dist/integrations/base/types.d.ts +43 -36
- package/dist/integrations/base/types.d.ts.map +1 -1
- package/dist/integrations/base/types.js +26 -1
- package/dist/integrations/base/types.js.map +1 -1
- package/dist/integrations/documentation-resolver.test.js +5 -11
- package/dist/integrations/documentation-resolver.test.js.map +1 -1
- package/dist/integrations/restapiintegration/client.test.d.ts +10 -0
- package/dist/integrations/restapiintegration/client.test.d.ts.map +1 -1
- package/dist/integrations/restapiintegration/client.test.js +69 -146
- package/dist/integrations/restapiintegration/client.test.js.map +1 -1
- package/dist/integrations/slack/client.test.js +1 -26
- package/dist/integrations/slack/client.test.js.map +1 -1
- package/package.json +2 -2
- package/src/errors.ts +5 -33
- package/src/integrations/anthropic/README.md +0 -7
- package/src/integrations/base/rest-api-client-base.ts +56 -28
- package/src/integrations/base/rest-api-integration-client.ts +33 -14
- package/src/integrations/base/types.ts +45 -41
- package/src/integrations/bigquery/README.md +0 -1
- package/src/integrations/box/README.md +0 -3
- package/src/integrations/cohere/README.md +0 -7
- package/src/integrations/documentation-resolver.test.ts +6 -13
- package/src/integrations/fireworks/README.md +0 -7
- package/src/integrations/gemini/README.md +0 -8
- package/src/integrations/groq/README.md +0 -7
- package/src/integrations/mistral/README.md +0 -7
- package/src/integrations/openai_v2/README.md +0 -7
- package/src/integrations/perplexity/README.md +0 -7
- package/src/integrations/restapiintegration/client.test.ts +86 -207
- package/src/integrations/restapiintegration/docs.manifest.json +1 -5
- package/src/integrations/s3/README.md +0 -1
- package/src/integrations/slack/client.test.ts +1 -36
- package/src/integrations/snowflakecortex/README.md +0 -8
- package/src/integrations/stabilityai/README.md +0 -7
- package/dist/integrations/base/decode-worker-binary-response.d.ts +0 -4
- package/dist/integrations/base/decode-worker-binary-response.d.ts.map +0 -1
- package/dist/integrations/base/decode-worker-binary-response.js +0 -49
- package/dist/integrations/base/decode-worker-binary-response.js.map +0 -1
- package/dist/integrations/base/decode-worker-binary-response.test.d.ts +0 -2
- package/dist/integrations/base/decode-worker-binary-response.test.d.ts.map +0 -1
- package/dist/integrations/base/decode-worker-binary-response.test.js +0 -81
- package/dist/integrations/base/decode-worker-binary-response.test.js.map +0 -1
- package/src/integrations/base/decode-worker-binary-response.test.ts +0 -107
- package/src/integrations/base/decode-worker-binary-response.ts +0 -62
- package/src/integrations/restapiintegration/overlays/response-types-binary.md +0 -51
|
@@ -277,13 +277,6 @@ All methods accept an optional `metadata` parameter as the last argument for dia
|
|
|
277
277
|
|
|
278
278
|
## Common Pitfalls
|
|
279
279
|
|
|
280
|
-
### Streaming Is Not Supported
|
|
281
|
-
|
|
282
|
-
`apiRequest()` does not support streaming or Server-Sent Events. Do not set
|
|
283
|
-
`stream: true` — streaming responses fail schema validation. Every call
|
|
284
|
-
returns the complete response; if a UI needs real-time token streaming,
|
|
285
|
-
handle it at the frontend layer, not through the SDK.
|
|
286
|
-
|
|
287
280
|
### No Specialized Methods
|
|
288
281
|
|
|
289
282
|
```typescript
|
|
@@ -257,13 +257,6 @@ 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
|
-
|
|
267
260
|
### No Specialized Methods
|
|
268
261
|
|
|
269
262
|
The OpenAI client only provides `apiRequest()`. There are no other specialized methods:
|
|
@@ -189,13 +189,6 @@ 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
|
-
|
|
199
192
|
### No Specialized Methods
|
|
200
193
|
|
|
201
194
|
```typescript
|
|
@@ -1,13 +1,37 @@
|
|
|
1
|
-
|
|
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";
|
|
2
13
|
import { z } from "zod";
|
|
3
14
|
|
|
4
|
-
import
|
|
15
|
+
import { ActionResponseType } from "@superblocksteam/shared";
|
|
5
16
|
|
|
6
17
|
import { RestApiValidationError } from "../../errors.js";
|
|
7
|
-
import {
|
|
18
|
+
import type { RestApiResponseType } from "../base/types.js";
|
|
8
19
|
import type { IntegrationConfig } from "../types.js";
|
|
9
20
|
import { RestApiIntegrationPluginClientImpl } from "./client.js";
|
|
10
21
|
|
|
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
|
+
|
|
11
35
|
const TEST_CONFIG: IntegrationConfig = {
|
|
12
36
|
id: "restapi-test-id",
|
|
13
37
|
name: "Test REST API",
|
|
@@ -16,11 +40,6 @@ const TEST_CONFIG: IntegrationConfig = {
|
|
|
16
40
|
};
|
|
17
41
|
|
|
18
42
|
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
|
-
}
|
|
24
43
|
|
|
25
44
|
function createClient(mockResult: unknown) {
|
|
26
45
|
const executeQuery = vi.fn().mockResolvedValue(mockResult);
|
|
@@ -31,14 +50,9 @@ function createClient(mockResult: unknown) {
|
|
|
31
50
|
return { client, executeQuery };
|
|
32
51
|
}
|
|
33
52
|
|
|
34
|
-
function callApiRequest(
|
|
35
|
-
client: RestApiIntegrationPluginClientImpl,
|
|
36
|
-
...args: unknown[]
|
|
37
|
-
): Promise<unknown> {
|
|
38
|
-
return Reflect.apply(client.apiRequest, client, args);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
53
|
describe("RestApiIntegrationPluginClientImpl", () => {
|
|
54
|
+
// ── Default JSON behavior (must not regress) ────────────────────
|
|
55
|
+
|
|
42
56
|
describe("default JSON behavior", () => {
|
|
43
57
|
it("sends responseType json when the option is omitted", async () => {
|
|
44
58
|
const { client, executeQuery } = createClient({ id: "1" });
|
|
@@ -67,26 +81,19 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
67
81
|
});
|
|
68
82
|
|
|
69
83
|
it("throws RestApiValidationError when the response does not match the schema", async () => {
|
|
70
|
-
const
|
|
71
|
-
const { client } = createClient(payload);
|
|
84
|
+
const { client } = createClient(XML_RESPONSE);
|
|
72
85
|
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
await expect(
|
|
87
|
+
client.apiRequest(
|
|
75
88
|
{ method: "GET", path: "/users" },
|
|
76
89
|
{ response: z.object({ id: z.string() }) },
|
|
77
|
-
)
|
|
78
|
-
|
|
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");
|
|
90
|
+
),
|
|
91
|
+
).rejects.toThrow(RestApiValidationError);
|
|
87
92
|
});
|
|
88
93
|
});
|
|
89
94
|
|
|
95
|
+
// ── responseType passthrough ────────────────────────────────────
|
|
96
|
+
|
|
90
97
|
describe("responseType passthrough", () => {
|
|
91
98
|
it("forwards responseType text to the orchestrator request", async () => {
|
|
92
99
|
const { client, executeQuery } = createClient(XML_RESPONSE);
|
|
@@ -145,6 +152,8 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
145
152
|
});
|
|
146
153
|
|
|
147
154
|
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.
|
|
148
157
|
const { client } = createClient("");
|
|
149
158
|
|
|
150
159
|
const result = await client.apiRequest({
|
|
@@ -157,6 +166,10 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
157
166
|
});
|
|
158
167
|
|
|
159
168
|
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.
|
|
160
173
|
for (const badResult of [null, undefined]) {
|
|
161
174
|
const { client: textClient } = createClient(badResult);
|
|
162
175
|
await expect(
|
|
@@ -167,15 +180,8 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
167
180
|
}),
|
|
168
181
|
).rejects.toThrow(RestApiValidationError);
|
|
169
182
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
binaryClient.apiRequest({
|
|
173
|
-
method: "GET",
|
|
174
|
-
path: "/broken",
|
|
175
|
-
responseType: "binary",
|
|
176
|
-
}),
|
|
177
|
-
).rejects.toThrow(RestApiValidationError);
|
|
178
|
-
|
|
183
|
+
// JSON mode always goes through the schema overload; the bad
|
|
184
|
+
// result is rejected before validation runs.
|
|
179
185
|
const { client: jsonClient } = createClient(badResult);
|
|
180
186
|
await expect(
|
|
181
187
|
jsonClient.apiRequest(
|
|
@@ -187,109 +193,14 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
187
193
|
});
|
|
188
194
|
});
|
|
189
195
|
|
|
190
|
-
|
|
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
|
-
});
|
|
196
|
+
// ── Request-body validation via the schema-less overload ────────
|
|
288
197
|
|
|
289
198
|
describe("request body validation", () => {
|
|
290
199
|
it("throws RestApiValidationError for an invalid body with a body-only schema", async () => {
|
|
291
200
|
const { client, executeQuery } = createClient({ ok: true });
|
|
292
201
|
|
|
202
|
+
// Runtime-invalid bodies reach this path via dynamic data (user
|
|
203
|
+
// input, parsed JSON) that the compiler cannot check.
|
|
293
204
|
const dynamicBody: unknown = JSON.parse('{"amount":"not-a-number"}');
|
|
294
205
|
|
|
295
206
|
await expect(
|
|
@@ -331,12 +242,23 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
331
242
|
});
|
|
332
243
|
});
|
|
333
244
|
|
|
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
|
+
|
|
334
251
|
describe("runtime contract enforcement", () => {
|
|
252
|
+
type UncheckedApiRequest = (...args: unknown[]) => Promise<unknown>;
|
|
253
|
+
|
|
335
254
|
it("rejects a schema-less call in default JSON mode without issuing the request", async () => {
|
|
336
255
|
const { client, executeQuery } = createClient({ ok: true });
|
|
337
256
|
|
|
338
257
|
await expect(
|
|
339
|
-
|
|
258
|
+
(client.apiRequest as UncheckedApiRequest)({
|
|
259
|
+
method: "GET",
|
|
260
|
+
path: "/users",
|
|
261
|
+
}),
|
|
340
262
|
).rejects.toThrow(RestApiValidationError);
|
|
341
263
|
expect(executeQuery).not.toHaveBeenCalled();
|
|
342
264
|
});
|
|
@@ -345,8 +267,7 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
345
267
|
const { client, executeQuery } = createClient({ ok: true });
|
|
346
268
|
|
|
347
269
|
await expect(
|
|
348
|
-
|
|
349
|
-
client,
|
|
270
|
+
(client.apiRequest as UncheckedApiRequest)(
|
|
350
271
|
{
|
|
351
272
|
method: "POST",
|
|
352
273
|
path: "/orders",
|
|
@@ -359,32 +280,27 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
359
280
|
expect(executeQuery).not.toHaveBeenCalled();
|
|
360
281
|
});
|
|
361
282
|
|
|
362
|
-
it("rejects
|
|
363
|
-
|
|
364
|
-
const { client, executeQuery } = createClient({ ok: true });
|
|
283
|
+
it("rejects an unexposed responseType value without issuing the request", async () => {
|
|
284
|
+
const { client, executeQuery } = createClient({ ok: true });
|
|
365
285
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
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();
|
|
375
294
|
});
|
|
376
295
|
});
|
|
377
296
|
|
|
378
|
-
|
|
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
|
-
});
|
|
297
|
+
// ── Compile-time overload contracts (verified by `pnpm typecheck`) ──
|
|
384
298
|
|
|
299
|
+
describe("overload resolution", () => {
|
|
385
300
|
it("keeps typed results for schema callers and unknown for schema-less calls", async () => {
|
|
386
301
|
const { client } = createClient({ id: "1" });
|
|
387
302
|
|
|
303
|
+
// With a response schema: resolves to the typed overload.
|
|
388
304
|
const typed: Promise<{ id: string }> = client.apiRequest(
|
|
389
305
|
{ method: "GET", path: "/users" },
|
|
390
306
|
{ response: z.object({ id: z.string() }) },
|
|
@@ -393,6 +309,7 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
393
309
|
|
|
394
310
|
const { client: rawClient } = createClient(XML_RESPONSE);
|
|
395
311
|
|
|
312
|
+
// Without a schema: resolves to the raw overload returning unknown.
|
|
396
313
|
const raw: Promise<unknown> = rawClient.apiRequest({
|
|
397
314
|
method: "GET",
|
|
398
315
|
path: "/report.xml",
|
|
@@ -408,58 +325,20 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
408
325
|
});
|
|
409
326
|
await wrong;
|
|
410
327
|
|
|
411
|
-
//
|
|
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
|
|
412
334
|
const unvalidatedJson = rawClient.apiRequest({
|
|
413
335
|
method: "GET",
|
|
414
336
|
path: "/users",
|
|
415
337
|
});
|
|
416
338
|
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
|
-
);
|
|
445
339
|
|
|
446
|
-
//
|
|
447
|
-
|
|
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);
|
|
340
|
+
// "auto" and "binary" are valid wire values but deliberately not
|
|
341
|
+
// exposed in the SDK union (see RestApiResponseType).
|
|
463
342
|
const autoCall = rawClient.apiRequest({
|
|
464
343
|
method: "GET",
|
|
465
344
|
path: "/report.xml",
|
|
@@ -468,13 +347,13 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
468
347
|
});
|
|
469
348
|
await expect(autoCall).rejects.toThrow(RestApiValidationError);
|
|
470
349
|
|
|
471
|
-
const
|
|
350
|
+
const binaryCall = rawClient.apiRequest({
|
|
472
351
|
method: "GET",
|
|
473
352
|
path: "/report.xml",
|
|
474
|
-
// @ts-expect-error "
|
|
475
|
-
responseType: "
|
|
353
|
+
// @ts-expect-error "binary" is not an exposed responseType
|
|
354
|
+
responseType: "binary",
|
|
476
355
|
});
|
|
477
|
-
await expect(
|
|
356
|
+
await expect(binaryCall).rejects.toThrow(RestApiValidationError);
|
|
478
357
|
});
|
|
479
358
|
});
|
|
480
359
|
});
|
|
@@ -4,11 +4,7 @@
|
|
|
4
4
|
"overlays": [
|
|
5
5
|
{
|
|
6
6
|
"file": "overlays/response-types.md",
|
|
7
|
-
"sdkVersionRange": ">=0.0.3
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"file": "overlays/response-types-binary.md",
|
|
11
|
-
"sdkVersionRange": ">=0.0.4"
|
|
7
|
+
"sdkVersionRange": ">=0.0.3"
|
|
12
8
|
},
|
|
13
9
|
{
|
|
14
10
|
"file": "overlays/response-types-unsupported.md",
|
|
@@ -12,7 +12,6 @@ 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";
|
|
16
15
|
import type { IntegrationConfig } from "../types.js";
|
|
17
16
|
import { SlackClientImpl } from "./client.js";
|
|
18
17
|
import type { SlackErrorResponse } from "./types.js";
|
|
@@ -509,41 +508,7 @@ describe("SlackClientImpl", () => {
|
|
|
509
508
|
});
|
|
510
509
|
});
|
|
511
510
|
|
|
512
|
-
|
|
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
|
-
});
|
|
511
|
+
// ── Type narrowing (compile-time verification) ─────────────────
|
|
547
512
|
|
|
548
513
|
describe("type narrowing", () => {
|
|
549
514
|
it("narrows to error response fields when ok is false", async () => {
|
|
@@ -108,14 +108,6 @@ 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
|
-
|
|
119
111
|
### No Specialized Methods
|
|
120
112
|
|
|
121
113
|
The Snowflake Cortex client only provides `apiRequest()`. There are no other specialized methods:
|
|
@@ -234,13 +234,6 @@ 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
|
-
|
|
244
237
|
### No Specialized Methods
|
|
245
238
|
|
|
246
239
|
```typescript
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"decode-worker-binary-response.d.ts","sourceRoot":"","sources":["../../../src/integrations/base/decode-worker-binary-response.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,6BAA6B,EAE9B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,6BAA6B,EAAE,CAAC;AA0CzC,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAYrE"}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { REDACTED_BINARY_RESPONSE_DATA, RestApiValidationError, } from "../../errors.js";
|
|
3
|
-
export { REDACTED_BINARY_RESPONSE_DATA };
|
|
4
|
-
const workerBinaryResponseSchema = z.union([
|
|
5
|
-
z.instanceof(Uint8Array).transform((value) => new Uint8Array(value)),
|
|
6
|
-
z
|
|
7
|
-
.object({
|
|
8
|
-
data: z.unknown(),
|
|
9
|
-
type: z.literal("Buffer"),
|
|
10
|
-
})
|
|
11
|
-
.transform((value, context) => {
|
|
12
|
-
if (!Array.isArray(value.data)) {
|
|
13
|
-
context.addIssue({
|
|
14
|
-
code: z.ZodIssueCode.custom,
|
|
15
|
-
message: "data must be an array of bytes",
|
|
16
|
-
path: ["data"],
|
|
17
|
-
});
|
|
18
|
-
return z.NEVER;
|
|
19
|
-
}
|
|
20
|
-
const bytes = new Uint8Array(value.data.length);
|
|
21
|
-
for (let index = 0; index < value.data.length; index += 1) {
|
|
22
|
-
const item = value.data[index];
|
|
23
|
-
if (typeof item !== "number" ||
|
|
24
|
-
!Number.isInteger(item) ||
|
|
25
|
-
item < 0 ||
|
|
26
|
-
item > 255) {
|
|
27
|
-
context.addIssue({
|
|
28
|
-
code: z.ZodIssueCode.custom,
|
|
29
|
-
message: "byte must be an integer from 0 through 255",
|
|
30
|
-
path: ["data", index],
|
|
31
|
-
});
|
|
32
|
-
return z.NEVER;
|
|
33
|
-
}
|
|
34
|
-
bytes[index] = item;
|
|
35
|
-
}
|
|
36
|
-
return bytes;
|
|
37
|
-
}),
|
|
38
|
-
]);
|
|
39
|
-
export function decodeWorkerBinaryResponse(value) {
|
|
40
|
-
const result = workerBinaryResponseSchema.safeParse(value);
|
|
41
|
-
if (!result.success) {
|
|
42
|
-
throw new RestApiValidationError(`Binary response is malformed: ${result.error.message}`, {
|
|
43
|
-
data: REDACTED_BINARY_RESPONSE_DATA,
|
|
44
|
-
zodError: result.error,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
return result.data;
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=decode-worker-binary-response.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"decode-worker-binary-response.js","sourceRoot":"","sources":["../../../src/integrations/base/decode-worker-binary-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,6BAA6B,EAAE,CAAC;AAEzC,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC;IACzC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC;SACE,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;QACjB,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;KAC1B,CAAC;SACD,SAAS,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,QAAQ,CAAC;gBACf,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;gBAC3B,OAAO,EAAE,gCAAgC;gBACzC,IAAI,EAAE,CAAC,MAAM,CAAC;aACf,CAAC,CAAC;YACH,OAAO,CAAC,CAAC,KAAK,CAAC;QACjB,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC/B,IACE,OAAO,IAAI,KAAK,QAAQ;gBACxB,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;gBACvB,IAAI,GAAG,CAAC;gBACR,IAAI,GAAG,GAAG,EACV,CAAC;gBACD,OAAO,CAAC,QAAQ,CAAC;oBACf,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;oBAC3B,OAAO,EAAE,4CAA4C;oBACrD,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;iBACtB,CAAC,CAAC;gBACH,OAAO,CAAC,CAAC,KAAK,CAAC;YACjB,CAAC;YACD,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;QACtB,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;CACL,CAAC,CAAC;AAEH,MAAM,UAAU,0BAA0B,CAAC,KAAc;IACvD,MAAM,MAAM,GAAG,0BAA0B,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,sBAAsB,CAC9B,iCAAiC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EACvD;YACE,IAAI,EAAE,6BAA6B;YACnC,QAAQ,EAAE,MAAM,CAAC,KAAK;SACvB,CACF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"decode-worker-binary-response.test.d.ts","sourceRoot":"","sources":["../../../src/integrations/base/decode-worker-binary-response.test.ts"],"names":[],"mappings":""}
|