@superblocksteam/sdk-api 0.0.12 → 2.0.0-SNAPSHOT.1d540461e97d05b4

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 (107) hide show
  1. package/dist/errors.d.ts +28 -3
  2. package/dist/errors.d.ts.map +1 -1
  3. package/dist/errors.js +187 -8
  4. package/dist/errors.js.map +1 -1
  5. package/dist/errors.test.d.ts +2 -0
  6. package/dist/errors.test.d.ts.map +1 -0
  7. package/dist/errors.test.js +189 -0
  8. package/dist/errors.test.js.map +1 -0
  9. package/dist/integrations/anthropic/types.d.ts +1 -1
  10. package/dist/integrations/base/decode-worker-binary-response.d.ts.map +1 -1
  11. package/dist/integrations/base/decode-worker-binary-response.js +2 -5
  12. package/dist/integrations/base/decode-worker-binary-response.js.map +1 -1
  13. package/dist/integrations/base/index.d.ts +2 -1
  14. package/dist/integrations/base/index.d.ts.map +1 -1
  15. package/dist/integrations/base/index.js +1 -0
  16. package/dist/integrations/base/index.js.map +1 -1
  17. package/dist/integrations/base/request-body.d.ts +29 -0
  18. package/dist/integrations/base/request-body.d.ts.map +1 -0
  19. package/dist/integrations/base/request-body.js +217 -0
  20. package/dist/integrations/base/request-body.js.map +1 -0
  21. package/dist/integrations/base/request-body.test.d.ts +2 -0
  22. package/dist/integrations/base/request-body.test.d.ts.map +1 -0
  23. package/dist/integrations/base/request-body.test.js +448 -0
  24. package/dist/integrations/base/request-body.test.js.map +1 -0
  25. package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
  26. package/dist/integrations/base/rest-api-client-base.js +15 -14
  27. package/dist/integrations/base/rest-api-client-base.js.map +1 -1
  28. package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
  29. package/dist/integrations/base/rest-api-integration-client.js +2 -5
  30. package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
  31. package/dist/integrations/base/types.d.ts +32 -5
  32. package/dist/integrations/base/types.d.ts.map +1 -1
  33. package/dist/integrations/base/types.js +7 -0
  34. package/dist/integrations/base/types.js.map +1 -1
  35. package/dist/integrations/cosmosdb/client.d.ts.map +1 -1
  36. package/dist/integrations/cosmosdb/client.js +2 -5
  37. package/dist/integrations/cosmosdb/client.js.map +1 -1
  38. package/dist/integrations/documentation-resolver.test.js +53 -1
  39. package/dist/integrations/documentation-resolver.test.js.map +1 -1
  40. package/dist/integrations/documentation.test.js +10 -0
  41. package/dist/integrations/documentation.test.js.map +1 -1
  42. package/dist/integrations/dynamodb/client.d.ts.map +1 -1
  43. package/dist/integrations/dynamodb/client.js +2 -5
  44. package/dist/integrations/dynamodb/client.js.map +1 -1
  45. package/dist/integrations/gcs/client.d.ts.map +1 -1
  46. package/dist/integrations/gcs/client.js +2 -5
  47. package/dist/integrations/gcs/client.js.map +1 -1
  48. package/dist/integrations/gsheets/client.d.ts.map +1 -1
  49. package/dist/integrations/gsheets/client.js +2 -5
  50. package/dist/integrations/gsheets/client.js.map +1 -1
  51. package/dist/integrations/mongodb/client.d.ts.map +1 -1
  52. package/dist/integrations/mongodb/client.js +2 -5
  53. package/dist/integrations/mongodb/client.js.map +1 -1
  54. package/dist/integrations/restapiintegration/client.body-types.test.d.ts +2 -0
  55. package/dist/integrations/restapiintegration/client.body-types.test.d.ts.map +1 -0
  56. package/dist/integrations/restapiintegration/client.body-types.test.js +227 -0
  57. package/dist/integrations/restapiintegration/client.body-types.test.js.map +1 -0
  58. package/dist/integrations/restapiintegration/client.test.js +16 -0
  59. package/dist/integrations/restapiintegration/client.test.js.map +1 -1
  60. package/dist/integrations/s3/client.d.ts.map +1 -1
  61. package/dist/integrations/s3/client.js +2 -5
  62. package/dist/integrations/s3/client.js.map +1 -1
  63. package/dist/integrations/salesforce/client.d.ts.map +1 -1
  64. package/dist/integrations/salesforce/client.js +2 -5
  65. package/dist/integrations/salesforce/client.js.map +1 -1
  66. package/dist/integrations/slack/client.d.ts.map +1 -1
  67. package/dist/integrations/slack/client.js +4 -13
  68. package/dist/integrations/slack/client.js.map +1 -1
  69. package/dist/integrations/slack/client.test.js +20 -0
  70. package/dist/integrations/slack/client.test.js.map +1 -1
  71. package/dist/integrations/utils.d.ts +8 -3
  72. package/dist/integrations/utils.d.ts.map +1 -1
  73. package/dist/integrations/utils.js +42 -1
  74. package/dist/integrations/utils.js.map +1 -1
  75. package/package.json +6 -3
  76. package/src/errors.test.ts +257 -0
  77. package/src/errors.ts +214 -10
  78. package/src/integrations/anthropic/README.md +12 -59
  79. package/src/integrations/anthropic/types.ts +1 -1
  80. package/src/integrations/base/decode-worker-binary-response.ts +5 -7
  81. package/src/integrations/base/index.ts +2 -0
  82. package/src/integrations/base/request-body.test.ts +535 -0
  83. package/src/integrations/base/request-body.ts +368 -0
  84. package/src/integrations/base/rest-api-client-base.ts +24 -19
  85. package/src/integrations/base/rest-api-integration-client.ts +8 -7
  86. package/src/integrations/base/types.ts +41 -5
  87. package/src/integrations/cosmosdb/client.ts +8 -7
  88. package/src/integrations/documentation-resolver.test.ts +65 -1
  89. package/src/integrations/documentation.test.ts +12 -0
  90. package/src/integrations/dynamodb/client.ts +8 -7
  91. package/src/integrations/gcs/client.ts +8 -7
  92. package/src/integrations/gsheets/client.ts +8 -7
  93. package/src/integrations/jira/README.md +0 -26
  94. package/src/integrations/jira/docs.manifest.json +10 -1
  95. package/src/integrations/jira/overlays/attachments-unsupported.md +3 -0
  96. package/src/integrations/jira/overlays/attachments.md +30 -0
  97. package/src/integrations/mongodb/client.ts +8 -7
  98. package/src/integrations/restapiintegration/client.body-types.test.ts +323 -0
  99. package/src/integrations/restapiintegration/client.test.ts +23 -0
  100. package/src/integrations/restapiintegration/docs.manifest.json +8 -0
  101. package/src/integrations/restapiintegration/overlays/request-body-types-unsupported.md +7 -0
  102. package/src/integrations/restapiintegration/overlays/request-body-types.md +115 -0
  103. package/src/integrations/s3/client.ts +8 -7
  104. package/src/integrations/salesforce/client.ts +9 -7
  105. package/src/integrations/slack/client.test.ts +30 -0
  106. package/src/integrations/slack/client.ts +13 -19
  107. package/src/integrations/utils.ts +51 -1
@@ -0,0 +1,115 @@
1
+ ## Request Body Types
2
+
3
+ `apiRequest()` serializes request bodies as JSON by default. For endpoints that expect another encoding, pass `bodyType`:
4
+
5
+ - `"json"` for JSON payloads. This is the default.
6
+ - `"raw"` for caller-encoded string bodies such as XML, CSV, or plain text.
7
+ - `"formUrlencoded"` for `application/x-www-form-urlencoded` payloads.
8
+ - `"multipartForm"` for multipart fields and file parts.
9
+ - `"binary"` for byte payloads. Pass bytes such as a `Uint8Array`, `ArrayBuffer`, `DataView`, or `Buffer`.
10
+
11
+ ### Form URL-Encoded Requests
12
+
13
+ Use `bodyType: "formUrlencoded"` for OAuth token exchanges and other APIs that expect HTML form encoding. Arrays repeat the key, so `tags: ["a", "b"]` encodes as `tags=a&tags=b`. APIs that expect bracket keys need the brackets in the key, and nested fields must be flattened with bracket notation:
14
+
15
+ ```typescript
16
+ const token = await ctx.integrations.authApi.apiRequest(
17
+ {
18
+ method: "POST",
19
+ path: "/oauth/token",
20
+ bodyType: "formUrlencoded",
21
+ body: {
22
+ grant_type: "client_credentials",
23
+ client_id: input.clientId,
24
+ scope: "read:orders write:orders",
25
+ },
26
+ },
27
+ { response: TokenResponseSchema },
28
+ );
29
+
30
+ const order = await ctx.integrations.ordersApi.apiRequest(
31
+ {
32
+ method: "POST",
33
+ path: "/v1/orders",
34
+ bodyType: "formUrlencoded",
35
+ body: {
36
+ amount: 2000,
37
+ currency: "usd",
38
+ "metadata[order_id]": "6735",
39
+ },
40
+ },
41
+ { response: OrderResponseSchema },
42
+ );
43
+ ```
44
+
45
+ The SDK adds `Content-Type: application/x-www-form-urlencoded` unless you already provided a `Content-Type` header.
46
+ Fields whose value is `undefined` are omitted. Empty arrays are omitted. The body must encode at least one field.
47
+
48
+ ### Binary Uploads
49
+
50
+ Use `bodyType: "binary"` for APIs that accept the request body as bytes, such as SharePoint-style upload endpoints. For an uploaded file, pass the `Uint8Array` returned by `await file.readContentsAsync("raw")` directly as the body:
51
+
52
+ ```typescript
53
+ const file = input.upload.files[0];
54
+
55
+ await ctx.integrations.sharepoint.apiRequest(
56
+ {
57
+ method: "PUT",
58
+ path: `/sites/${siteId}/drive/root:/reports/${file.name}:/content`,
59
+ bodyType: "binary",
60
+ body: await file.readContentsAsync("raw"),
61
+ headers: { "Content-Type": file.type || "application/octet-stream" },
62
+ },
63
+ { response: UploadResponseSchema },
64
+ );
65
+ ```
66
+
67
+ For bytes that do not come from an uploaded file, pass a byte container supported by the SDK, such as `Uint8Array`, `ArrayBuffer`, `DataView`, or `Buffer`. Do not convert file bytes to a string. `readContentsAsync("binary")` returns base64 text, not bytes. Never send that text as a file body. Use `readContentsAsync("raw")` instead. If only base64 text is available, decode it with `Buffer.from(text, "base64")` and send the result with `bodyType: "binary"`.
68
+
69
+ The SDK sends the exact byte window of a byte view, so `bytes.subarray(start, end)` uploads a slice. It adds `Content-Type: application/octet-stream` unless you already provided a `Content-Type` header such as `application/pdf`.
70
+
71
+ Binary request bodies must be non-empty; a zero-length body throws a validation error.
72
+
73
+ ### Multipart Uploads
74
+
75
+ Use `bodyType: "multipartForm"` for endpoints that expect fields plus file parts. File parts must be wrapped as `{ value, filename }`; a bare binary value without that wrapper is rejected with a message naming the required `{ value, filename }` shape. A file part `value` is a string or bytes such as the `Uint8Array` returned by `await file.readContentsAsync("raw")`. `readContentsAsync("binary")` returns base64 text, not bytes; never pass that text as a file part value, it will be UTF-8 encoded a second time and silently corrupt the upload. Use `readContentsAsync("raw")` for file part bytes.
76
+
77
+ ```typescript
78
+ const invoice = input.upload.files[0];
79
+
80
+ const upload = await ctx.integrations.filesApi.apiRequest(
81
+ {
82
+ method: "POST",
83
+ path: "/files",
84
+ bodyType: "multipartForm",
85
+ body: {
86
+ folder: "invoices",
87
+ invoice: {
88
+ value: await invoice.readContentsAsync("raw"),
89
+ filename: invoice.name,
90
+ },
91
+ },
92
+ },
93
+ { response: UploadResponseSchema },
94
+ );
95
+ ```
96
+
97
+ Do not set `Content-Type` for multipart requests. The worker generates the multipart boundary during execution.
98
+ Do not set per-part `contentType`. Arrays repeat the key, so `files: [partA, partB]` sends two `files` parts. Fields whose value is `undefined` are omitted. Empty file parts are allowed.
99
+
100
+ ### Raw String Bodies
101
+
102
+ Use `bodyType: "raw"` for pre-encoded text bodies. The SDK does not add a `Content-Type` header in raw mode, so provide the header expected by the API. Prefer `"json"`, `"formUrlencoded"`, `"multipartForm"`, or `"binary"` when the target API fits one of those shapes; reach for `"raw"` only when the API needs a pre-encoded format none of the typed body types cover, such as SOAP/XML or a signed payload:
103
+
104
+ ```typescript
105
+ await ctx.integrations.legacyApi.apiRequest(
106
+ {
107
+ method: "POST",
108
+ path: "/soap",
109
+ bodyType: "raw",
110
+ body: `<Envelope><Body>${payload}</Body></Envelope>`,
111
+ headers: { "Content-Type": "text/xml" },
112
+ },
113
+ { response: SoapResponseSchema },
114
+ );
115
+ ```
@@ -9,7 +9,10 @@ import type { z } from "zod";
9
9
 
10
10
  import type { Plugin as S3Plugin } from "@superblocksteam/types/dist/src/plugins/s3/v1/plugin_pb";
11
11
 
12
- import { RestApiValidationError } from "../../errors.js";
12
+ import {
13
+ RestApiValidationError,
14
+ restApiValidationErrorFromZodError,
15
+ } from "../../errors.js";
13
16
  import { IntegrationError } from "../../runtime/errors.js";
14
17
  import type { QueryExecutor, TraceMetadata } from "../registry.js";
15
18
  import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
@@ -71,12 +74,10 @@ export class S3ClientImpl implements S3Client, IntegrationClientImpl {
71
74
  const parseResult = schema.safeParse(result);
72
75
 
73
76
  if (!parseResult.success) {
74
- throw new RestApiValidationError(
75
- `Result validation failed: ${parseResult.error.message}`,
76
- {
77
- zodError: parseResult.error,
78
- data: result,
79
- },
77
+ throw restApiValidationErrorFromZodError(
78
+ "Result validation failed",
79
+ parseResult.error,
80
+ result,
80
81
  );
81
82
  }
82
83
 
@@ -7,7 +7,11 @@
7
7
 
8
8
  import type { z } from "zod";
9
9
 
10
- import { QueryValidationError, RestApiValidationError } from "../../errors.js";
10
+ import {
11
+ QueryValidationError,
12
+ RestApiValidationError,
13
+ restApiValidationErrorFromZodError,
14
+ } from "../../errors.js";
11
15
  import { IntegrationError } from "../../runtime/errors.js";
12
16
  import type { QueryExecutor, TraceMetadata } from "../registry.js";
13
17
  import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
@@ -205,12 +209,10 @@ export class SalesforceClientImpl
205
209
 
206
210
  const parseResult = schema.safeParse(result);
207
211
  if (!parseResult.success) {
208
- throw new RestApiValidationError(
209
- `Result validation failed: ${parseResult.error.message}`,
210
- {
211
- zodError: parseResult.error,
212
- data: result,
213
- },
212
+ throw restApiValidationErrorFromZodError(
213
+ "Result validation failed",
214
+ parseResult.error,
215
+ result,
214
216
  );
215
217
  }
216
218
 
@@ -307,6 +307,36 @@ describe("SlackClientImpl", () => {
307
307
  expect(request.params).toEqual([{ key: "unfurl_links", value: "false" }]);
308
308
  });
309
309
 
310
+ it("sends formUrlencoded bodies through the shared request builder", async () => {
311
+ const { client, executeQuery } = createClient({
312
+ ok: true,
313
+ channel: "C123",
314
+ ts: "1234567890.123456",
315
+ });
316
+
317
+ await client.apiRequest(
318
+ {
319
+ method: "POST",
320
+ path: "/chat.postMessage",
321
+ body: { channel: "#alerts", text: "hello" },
322
+ bodyType: "formUrlencoded",
323
+ },
324
+ { response: PostMessageSchema },
325
+ );
326
+
327
+ expect(executeQuery).toHaveBeenCalledWith(
328
+ expect.objectContaining({
329
+ body: expect.any(String),
330
+ bodyType: "rawBody",
331
+ headers: [
332
+ { key: "Content-Type", value: "application/x-www-form-urlencoded" },
333
+ ],
334
+ }),
335
+ undefined,
336
+ undefined,
337
+ );
338
+ });
339
+
310
340
  it("passes trace metadata to executeQuery", async () => {
311
341
  const { client, executeQuery } = createClient({
312
342
  ok: true,
@@ -8,7 +8,7 @@
8
8
 
9
9
  import type { z } from "zod";
10
10
 
11
- import { RestApiValidationError } from "../../errors.js";
11
+ import { restApiValidationErrorFromZodError } from "../../errors.js";
12
12
  import { RestApiClientBase } from "../base/rest-api-client-base.js";
13
13
  import type { ApiRequestOptions } from "../base/types.js";
14
14
  import type { TraceMetadata } from "../registry.js";
@@ -51,24 +51,20 @@ export class SlackClientImpl extends RestApiClientBase implements SlackClient {
51
51
  "ok" in result &&
52
52
  (result as Record<string, unknown>).ok === false
53
53
  ) {
54
- throw new RestApiValidationError(
55
- `Slack returned ok: false but the response did not match the expected error shape: ${errorResult.error.message}`,
56
- {
57
- zodError: errorResult.error,
58
- data: result,
59
- },
54
+ throw restApiValidationErrorFromZodError(
55
+ "Slack returned ok: false but the response did not match the expected error shape",
56
+ errorResult.error,
57
+ result,
60
58
  );
61
59
  }
62
60
 
63
61
  // Success responses must carry the Slack envelope discriminant.
64
62
  const successEnvelopeResult = SlackSuccessEnvelopeSchema.safeParse(result);
65
63
  if (!successEnvelopeResult.success) {
66
- throw new RestApiValidationError(
67
- `Slack success response is missing the expected ok: true envelope: ${successEnvelopeResult.error.message}`,
68
- {
69
- zodError: successEnvelopeResult.error,
70
- data: result,
71
- },
64
+ throw restApiValidationErrorFromZodError(
65
+ "Slack success response is missing the expected ok: true envelope",
66
+ successEnvelopeResult.error,
67
+ result,
72
68
  );
73
69
  }
74
70
 
@@ -81,12 +77,10 @@ export class SlackClientImpl extends RestApiClientBase implements SlackClient {
81
77
 
82
78
  const payloadResult = schema.response.safeParse(payloadCandidate);
83
79
  if (!payloadResult.success) {
84
- throw new RestApiValidationError(
85
- `Response validation failed: ${payloadResult.error.message}`,
86
- {
87
- zodError: payloadResult.error,
88
- data: result,
89
- },
80
+ throw restApiValidationErrorFromZodError(
81
+ "Response validation failed",
82
+ payloadResult.error,
83
+ result,
90
84
  );
91
85
  }
92
86
 
@@ -1,7 +1,23 @@
1
+ import { z } from "zod";
2
+
3
+ import { isBinaryData, RestApiValidationError } from "../errors.js";
4
+
5
+ const MAX_OPTION_VALUE_CHARS = 64;
6
+
1
7
  /**
2
8
  * Integration utility functions.
3
9
  */
4
10
 
11
+ /**
12
+ * Truncates a string to at most `max` characters, appending "..." when it
13
+ * was cut. The single place that defines how caller-supplied strings are
14
+ * bounded for display (`describeType`, `describeOptionValue`) and for the
15
+ * Zod issue `path` (`boundedPathSegment`).
16
+ */
17
+ function truncate(value: string, max = MAX_OPTION_VALUE_CHARS): string {
18
+ return value.length > max ? `${value.slice(0, max)}...` : value;
19
+ }
20
+
5
21
  /**
6
22
  * Produces a safe, human-readable description of a value's type
7
23
  * without exposing potentially sensitive data.
@@ -18,8 +34,42 @@ export function describeType(value: unknown): string {
18
34
  if (t === "object") {
19
35
  const keys = Object.keys(value as Record<string, unknown>);
20
36
  if (keys.length === 0) return "empty object";
21
- return `object with keys [${keys.slice(0, 5).join(", ")}${keys.length > 5 ? ", ..." : ""}]`;
37
+ const shown = keys.slice(0, 5).map((k) => truncate(k));
38
+ return `object with keys [${shown.join(", ")}${keys.length > 5 ? ", ..." : ""}]`;
22
39
  }
23
40
 
24
41
  return t;
25
42
  }
43
+
44
+ /**
45
+ * Describes a caller-supplied option value for an error message with bounded
46
+ * length. Strings are quoted and truncated; everything else is described by type
47
+ * so arbitrary payloads never land in the message.
48
+ */
49
+ export function describeOptionValue(value: unknown): string {
50
+ if (isBinaryData(value)) {
51
+ return `binary data (${value.byteLength} bytes)`;
52
+ }
53
+ if (typeof value !== "string") return describeType(value);
54
+ return JSON.stringify(truncate(value));
55
+ }
56
+
57
+ function boundedPathSegment(segment: string | number): string | number {
58
+ return typeof segment === "string" ? truncate(segment) : segment;
59
+ }
60
+
61
+ export function validationError(
62
+ message: string,
63
+ path: (string | number)[],
64
+ ): RestApiValidationError {
65
+ return new RestApiValidationError(message, {
66
+ data: undefined,
67
+ zodError: new z.ZodError([
68
+ {
69
+ code: z.ZodIssueCode.custom,
70
+ message,
71
+ path: path.map(boundedPathSegment),
72
+ },
73
+ ]),
74
+ });
75
+ }