@superblocksteam/sdk-api 2.0.104 → 2.0.105-next.1

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 (163) hide show
  1. package/README.md +202 -86
  2. package/dist/api/definition.d.ts +11 -6
  3. package/dist/api/definition.d.ts.map +1 -1
  4. package/dist/api/definition.js +19 -12
  5. package/dist/api/definition.js.map +1 -1
  6. package/dist/api/definition.test.js +39 -15
  7. package/dist/api/definition.test.js.map +1 -1
  8. package/dist/errors.d.ts +1 -1
  9. package/dist/errors.js +1 -1
  10. package/dist/index.d.ts +10 -10
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +9 -5
  13. package/dist/index.js.map +1 -1
  14. package/dist/integrations/base/index.d.ts +2 -1
  15. package/dist/integrations/base/index.d.ts.map +1 -1
  16. package/dist/integrations/base/index.js +1 -0
  17. package/dist/integrations/base/index.js.map +1 -1
  18. package/dist/integrations/base/rest-api-client-base.d.ts +48 -0
  19. package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -0
  20. package/dist/integrations/base/rest-api-client-base.js +98 -0
  21. package/dist/integrations/base/rest-api-client-base.js.map +1 -0
  22. package/dist/integrations/base/rest-api-integration-client.d.ts +10 -20
  23. package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
  24. package/dist/integrations/base/rest-api-integration-client.js +10 -65
  25. package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
  26. package/dist/integrations/box/types.d.ts +1 -1
  27. package/dist/integrations/declarations.d.ts +5 -63
  28. package/dist/integrations/declarations.d.ts.map +1 -1
  29. package/dist/integrations/declarations.js +5 -59
  30. package/dist/integrations/declarations.js.map +1 -1
  31. package/dist/integrations/documentation.test.js +0 -2
  32. package/dist/integrations/documentation.test.js.map +1 -1
  33. package/dist/integrations/googledrive/types.d.ts +1 -1
  34. package/dist/integrations/index.d.ts +1 -9
  35. package/dist/integrations/index.d.ts.map +1 -1
  36. package/dist/integrations/index.js +1 -6
  37. package/dist/integrations/index.js.map +1 -1
  38. package/dist/integrations/registry.d.ts +1 -10
  39. package/dist/integrations/registry.d.ts.map +1 -1
  40. package/dist/integrations/registry.js +0 -25
  41. package/dist/integrations/registry.js.map +1 -1
  42. package/dist/integrations/slack/client.d.ts +13 -9
  43. package/dist/integrations/slack/client.d.ts.map +1 -1
  44. package/dist/integrations/slack/client.js +60 -8
  45. package/dist/integrations/slack/client.js.map +1 -1
  46. package/dist/integrations/slack/client.test.d.ts +11 -0
  47. package/dist/integrations/slack/client.test.d.ts.map +1 -0
  48. package/dist/integrations/slack/client.test.js +368 -0
  49. package/dist/integrations/slack/client.test.js.map +1 -0
  50. package/dist/integrations/slack/index.d.ts +2 -1
  51. package/dist/integrations/slack/index.d.ts.map +1 -1
  52. package/dist/integrations/slack/index.js +1 -0
  53. package/dist/integrations/slack/index.js.map +1 -1
  54. package/dist/integrations/slack/types.d.ts +127 -28
  55. package/dist/integrations/slack/types.d.ts.map +1 -1
  56. package/dist/integrations/slack/types.js +27 -1
  57. package/dist/integrations/slack/types.js.map +1 -1
  58. package/dist/integrations/snowflake/client.d.ts +2 -2
  59. package/dist/integrations/snowflake/client.js +2 -2
  60. package/dist/runtime/context.d.ts +1 -1
  61. package/dist/runtime/executor.d.ts +2 -2
  62. package/dist/types.d.ts +14 -5
  63. package/dist/types.d.ts.map +1 -1
  64. package/package.json +2 -2
  65. package/src/api/definition.test.ts +40 -15
  66. package/src/api/definition.ts +19 -12
  67. package/src/errors.ts +1 -1
  68. package/src/index.ts +13 -30
  69. package/src/integrations/asana/README.md +12 -12
  70. package/src/integrations/base/index.ts +2 -1
  71. package/src/integrations/base/rest-api-client-base.ts +134 -0
  72. package/src/integrations/base/rest-api-integration-client.ts +12 -89
  73. package/src/integrations/bitbucket/README.md +19 -19
  74. package/src/integrations/box/README.md +24 -24
  75. package/src/integrations/box/types.ts +1 -1
  76. package/src/integrations/circleci/README.md +18 -18
  77. package/src/integrations/declarations.ts +5 -91
  78. package/src/integrations/documentation.test.ts +0 -2
  79. package/src/integrations/googledrive/README.md +25 -22
  80. package/src/integrations/googledrive/types.ts +1 -1
  81. package/src/integrations/graphql/README.md +2 -2
  82. package/src/integrations/index.ts +0 -45
  83. package/src/integrations/registry.ts +1 -34
  84. package/src/integrations/salesforce/README.md +11 -9
  85. package/src/integrations/slack/README.md +62 -19
  86. package/src/integrations/slack/client.test.ts +553 -0
  87. package/src/integrations/slack/client.ts +92 -12
  88. package/src/integrations/slack/index.ts +6 -1
  89. package/src/integrations/slack/types.ts +142 -29
  90. package/src/integrations/snowflake/client.ts +2 -2
  91. package/src/integrations/zoom/README.md +15 -15
  92. package/src/runtime/context.ts +1 -1
  93. package/src/runtime/executor.ts +2 -2
  94. package/src/types.ts +14 -5
  95. package/dist/integrations/couchbase/client.d.ts +0 -36
  96. package/dist/integrations/couchbase/client.d.ts.map +0 -1
  97. package/dist/integrations/couchbase/client.js +0 -148
  98. package/dist/integrations/couchbase/client.js.map +0 -1
  99. package/dist/integrations/couchbase/index.d.ts +0 -8
  100. package/dist/integrations/couchbase/index.d.ts.map +0 -1
  101. package/dist/integrations/couchbase/index.js +0 -7
  102. package/dist/integrations/couchbase/index.js.map +0 -1
  103. package/dist/integrations/couchbase/types.d.ts +0 -100
  104. package/dist/integrations/couchbase/types.d.ts.map +0 -1
  105. package/dist/integrations/couchbase/types.js +0 -5
  106. package/dist/integrations/couchbase/types.js.map +0 -1
  107. package/dist/integrations/kafka/client.d.ts +0 -25
  108. package/dist/integrations/kafka/client.d.ts.map +0 -1
  109. package/dist/integrations/kafka/client.js +0 -124
  110. package/dist/integrations/kafka/client.js.map +0 -1
  111. package/dist/integrations/kafka/index.d.ts +0 -8
  112. package/dist/integrations/kafka/index.d.ts.map +0 -1
  113. package/dist/integrations/kafka/index.js +0 -7
  114. package/dist/integrations/kafka/index.js.map +0 -1
  115. package/dist/integrations/kafka/types.d.ts +0 -113
  116. package/dist/integrations/kafka/types.d.ts.map +0 -1
  117. package/dist/integrations/kafka/types.js +0 -5
  118. package/dist/integrations/kafka/types.js.map +0 -1
  119. package/dist/integrations/kinesis/client.d.ts +0 -31
  120. package/dist/integrations/kinesis/client.d.ts.map +0 -1
  121. package/dist/integrations/kinesis/client.js +0 -101
  122. package/dist/integrations/kinesis/client.js.map +0 -1
  123. package/dist/integrations/kinesis/index.d.ts +0 -8
  124. package/dist/integrations/kinesis/index.d.ts.map +0 -1
  125. package/dist/integrations/kinesis/index.js +0 -7
  126. package/dist/integrations/kinesis/index.js.map +0 -1
  127. package/dist/integrations/kinesis/types.d.ts +0 -97
  128. package/dist/integrations/kinesis/types.d.ts.map +0 -1
  129. package/dist/integrations/kinesis/types.js +0 -7
  130. package/dist/integrations/kinesis/types.js.map +0 -1
  131. package/dist/integrations/python/client.d.ts +0 -42
  132. package/dist/integrations/python/client.d.ts.map +0 -1
  133. package/dist/integrations/python/client.js +0 -89
  134. package/dist/integrations/python/client.js.map +0 -1
  135. package/dist/integrations/python/client.test.d.ts +0 -5
  136. package/dist/integrations/python/client.test.d.ts.map +0 -1
  137. package/dist/integrations/python/client.test.js +0 -214
  138. package/dist/integrations/python/client.test.js.map +0 -1
  139. package/dist/integrations/python/index.d.ts +0 -6
  140. package/dist/integrations/python/index.d.ts.map +0 -1
  141. package/dist/integrations/python/index.js +0 -5
  142. package/dist/integrations/python/index.js.map +0 -1
  143. package/dist/integrations/python/types.d.ts +0 -85
  144. package/dist/integrations/python/types.d.ts.map +0 -1
  145. package/dist/integrations/python/types.js +0 -5
  146. package/dist/integrations/python/types.js.map +0 -1
  147. package/src/integrations/couchbase/README.md +0 -138
  148. package/src/integrations/couchbase/client.ts +0 -225
  149. package/src/integrations/couchbase/index.ts +0 -8
  150. package/src/integrations/couchbase/types.ts +0 -126
  151. package/src/integrations/kafka/README.md +0 -144
  152. package/src/integrations/kafka/client.ts +0 -216
  153. package/src/integrations/kafka/index.ts +0 -14
  154. package/src/integrations/kafka/types.ts +0 -128
  155. package/src/integrations/kinesis/README.md +0 -153
  156. package/src/integrations/kinesis/client.ts +0 -146
  157. package/src/integrations/kinesis/index.ts +0 -14
  158. package/src/integrations/kinesis/types.ts +0 -114
  159. package/src/integrations/python/README.md +0 -566
  160. package/src/integrations/python/client.test.ts +0 -341
  161. package/src/integrations/python/client.ts +0 -136
  162. package/src/integrations/python/index.ts +0 -6
  163. package/src/integrations/python/types.ts +0 -92
package/src/index.ts CHANGED
@@ -38,10 +38,14 @@
38
38
  * throw new Error('User not found');
39
39
  * }
40
40
  *
41
- * await ctx.integrations.notifier.postMessage({
42
- * channel: '#user-lookups',
43
- * text: `Fetched user ${user.name}`,
44
- * });
41
+ * const notifyResult = await ctx.integrations.notifier.apiRequest(
42
+ * { method: 'POST', path: '/chat.postMessage', body: { channel: '#user-lookups', text: `Fetched user ${user.name}` } },
43
+ * { response: z.object({ channel: z.string(), ts: z.string() }) }
44
+ * );
45
+ *
46
+ * if (!notifyResult.ok) {
47
+ * throw new Error(`Slack API error: ${notifyResult.error}`);
48
+ * }
45
49
  *
46
50
  * return { user };
47
51
  * },
@@ -129,20 +133,14 @@ export {
129
133
  mongodb,
130
134
  dynamodb,
131
135
  cosmosdb,
132
- couchbase,
133
136
  s3,
134
137
  gcs,
135
138
  googleSheets,
136
- kafka,
137
- kinesis,
138
139
  salesforce,
139
140
  redis,
140
141
  superblocksOcr,
141
- python,
142
142
  restApiIntegration,
143
143
  getIntegrationDeclarations,
144
- confluent,
145
- redpanda,
146
144
  lakebase,
147
145
  snowflakePostgres,
148
146
  smtp,
@@ -199,19 +197,13 @@ export {
199
197
  type MongoDBRef,
200
198
  type DynamoDBRef,
201
199
  type CosmosDBRef,
202
- type CouchbaseRef,
203
200
  type S3Ref,
204
201
  type GCSRef,
205
202
  type GoogleSheetsRef,
206
- type KafkaRef,
207
- type KinesisRef,
208
203
  type SalesforceRef,
209
204
  type RedisRef,
210
205
  type SuperblocksOCRRef,
211
- type PythonRef,
212
206
  type RestApiIntegrationRef,
213
- type ConfluentRef,
214
- type RedpandaRef,
215
207
  type LakebaseRef,
216
208
  type SnowflakePostgresRef,
217
209
  type SmtpRef,
@@ -219,7 +211,11 @@ export {
219
211
 
220
212
  // Integration client types (for advanced use cases)
221
213
  export type { PostgresClient } from "./integrations/postgres/index.js";
222
- export type { SlackClient } from "./integrations/slack/index.js";
214
+ export type {
215
+ SlackClient,
216
+ SlackResponse,
217
+ SlackErrorResponse,
218
+ } from "./integrations/slack/index.js";
223
219
  export type { SnowflakeClient } from "./integrations/snowflake/index.js";
224
220
  export type { SnowflakeCortexClient } from "./integrations/snowflakecortex/index.js";
225
221
  export type { AirtableClient } from "./integrations/airtable/index.js";
@@ -259,29 +255,16 @@ export type {
259
255
  DynamoDBAttributeValue,
260
256
  } from "./integrations/dynamodb/index.js";
261
257
  export type { CosmosDBClient } from "./integrations/cosmosdb/index.js";
262
- export type {
263
- CouchbaseClient,
264
- CouchbaseIdentifier,
265
- } from "./integrations/couchbase/index.js";
266
- export type {
267
- KinesisClient,
268
- KinesisPutParams,
269
- KinesisGetParams,
270
- KinesisStreamIdentifier,
271
- KinesisShardIteratorType,
272
- } from "./integrations/kinesis/index.js";
273
258
  export type {
274
259
  SalesforceClient,
275
260
  SalesforceCrudAction,
276
261
  SalesforceBulkAction,
277
262
  } from "./integrations/salesforce/index.js";
278
263
  export type { RedisClient } from "./integrations/redis/index.js";
279
- export type { KafkaClient } from "./integrations/kafka/index.js";
280
264
  export type { S3Client } from "./integrations/s3/index.js";
281
265
  export type { GCSClient } from "./integrations/gcs/index.js";
282
266
  export type { GoogleSheetsClient } from "./integrations/gsheets/index.js";
283
267
  export type { SuperblocksOCRClient } from "./integrations/superblocks-ocr/index.js";
284
- export type { PythonClient } from "./integrations/python/index.js";
285
268
  export type { RestApiIntegrationPluginClient } from "./integrations/restapiintegration/index.js";
286
269
  export type { LakebaseClient } from "./integrations/lakebase/index.js";
287
270
  export type { SnowflakePostgresClient } from "./integrations/snowflakepostgres/index.js";
@@ -49,7 +49,7 @@ export default api({
49
49
  const result = await ctx.integrations.asana.apiRequest(
50
50
  {
51
51
  method: "POST",
52
- path: "/api/1.0/tasks",
52
+ path: "/tasks",
53
53
  body: {
54
54
  data: {
55
55
  name: name,
@@ -88,7 +88,7 @@ const ListTasksResponseSchema = z.object({
88
88
  const result = await ctx.integrations.asana.apiRequest(
89
89
  {
90
90
  method: "GET",
91
- path: `/api/1.0/projects/${projectId}/tasks`,
91
+ path: `/projects/${projectId}/tasks`,
92
92
  params: {
93
93
  opt_fields: "name,completed,due_on,assignee.name",
94
94
  completed_since: "now", // Only incomplete tasks
@@ -108,7 +108,7 @@ result.data.forEach((task) => {
108
108
  const result = await ctx.integrations.asana.apiRequest(
109
109
  {
110
110
  method: "PUT",
111
- path: `/api/1.0/tasks/${taskId}`,
111
+ path: `/tasks/${taskId}`,
112
112
  body: {
113
113
  data: {
114
114
  completed: true,
@@ -126,7 +126,7 @@ const result = await ctx.integrations.asana.apiRequest(
126
126
  const result = await ctx.integrations.asana.apiRequest(
127
127
  {
128
128
  method: "PUT",
129
- path: `/api/1.0/tasks/${taskId}`,
129
+ path: `/tasks/${taskId}`,
130
130
  body: {
131
131
  data: {
132
132
  assignee: userId, // User GID
@@ -154,7 +154,7 @@ const ListProjectsResponseSchema = z.object({
154
154
  const result = await ctx.integrations.asana.apiRequest(
155
155
  {
156
156
  method: "GET",
157
- path: "/api/1.0/projects",
157
+ path: "/projects",
158
158
  params: {
159
159
  workspace: workspaceId,
160
160
  opt_fields: "name,archived,owner.name",
@@ -178,7 +178,7 @@ const CreateProjectResponseSchema = z.object({
178
178
  const result = await ctx.integrations.asana.apiRequest(
179
179
  {
180
180
  method: "POST",
181
- path: "/api/1.0/projects",
181
+ path: "/projects",
182
182
  body: {
183
183
  data: {
184
184
  name: "Q1 Marketing Campaign",
@@ -207,7 +207,7 @@ const StoryResponseSchema = z.object({
207
207
  const result = await ctx.integrations.asana.apiRequest(
208
208
  {
209
209
  method: "POST",
210
- path: `/api/1.0/tasks/${taskId}/stories`,
210
+ path: `/tasks/${taskId}/stories`,
211
211
  body: {
212
212
  data: {
213
213
  text: "Great progress on this task!",
@@ -233,7 +233,7 @@ const SearchResponseSchema = z.object({
233
233
  const result = await ctx.integrations.asana.apiRequest(
234
234
  {
235
235
  method: "GET",
236
- path: "/api/1.0/workspaces/{workspace_gid}/tasks/search",
236
+ path: "/workspaces/{workspace_gid}/tasks/search",
237
237
  params: {
238
238
  "text.value": "urgent",
239
239
  completed: false,
@@ -252,7 +252,7 @@ const result = await ctx.integrations.asana.apiRequest(
252
252
  const result = await ctx.integrations.asana.apiRequest(
253
253
  {
254
254
  method: "GET",
255
- path: `/api/1.0/tasks/${taskId}/subtasks`,
255
+ path: `/tasks/${taskId}/subtasks`,
256
256
  params: {
257
257
  opt_fields: "name,completed,assignee.name",
258
258
  },
@@ -276,7 +276,7 @@ await asana.getTasks({ ... });
276
276
 
277
277
  // CORRECT - Use apiRequest
278
278
  await ctx.integrations.asana.apiRequest(
279
- { method: "POST", path: "/api/1.0/tasks", body: { data: { ... } } },
279
+ { method: "POST", path: "/tasks", body: { data: { ... } } },
280
280
  { response: CreateTaskResponseSchema }
281
281
  );
282
282
  ```
@@ -381,7 +381,7 @@ async function getAllTasks(asana: AsanaClient, projectId: string) {
381
381
  const result = await ctx.integrations.asana.apiRequest(
382
382
  {
383
383
  method: "GET",
384
- path: `/api/1.0/projects/${projectId}/tasks`,
384
+ path: `/projects/${projectId}/tasks`,
385
385
  params: {
386
386
  limit: 100,
387
387
  ...(offset && { offset }),
@@ -405,7 +405,7 @@ import { RestApiValidationError } from "@superblocksteam/sdk-api";
405
405
 
406
406
  try {
407
407
  const result = await ctx.integrations.asana.apiRequest(
408
- { method: "POST", path: "/api/1.0/tasks", body: { data: { ... } } },
408
+ { method: "POST", path: "/tasks", body: { data: { ... } } },
409
409
  { response: CreateTaskResponseSchema }
410
410
  );
411
411
  } catch (error) {
@@ -2,8 +2,9 @@
2
2
  * Base clients for integrations.
3
3
  */
4
4
 
5
+ export { RestApiClientBase } from "./rest-api-client-base.js";
5
6
  export { RestApiIntegrationClient } from "./rest-api-integration-client.js";
6
- export type { RestApiRequest } from "./rest-api-integration-client.js";
7
+ export type { RestApiRequest } from "./rest-api-client-base.js";
7
8
  export { GraphQLIntegrationClient } from "./graphql-integration-client.js";
8
9
  export type {
9
10
  ApiRequestOptions,
@@ -0,0 +1,134 @@
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
+ import type { PartialMessage } from "@bufbuild/protobuf";
12
+ import { z } from "zod";
13
+
14
+ import type { Property } from "@superblocksteam/types/dist/src/common/v1/plugin_pb";
15
+ import type { Plugin as RestApiIntegrationPlugin } from "@superblocksteam/types/dist/src/plugins/restapiintegration/v1/plugin_pb";
16
+
17
+ import { RestApiValidationError } from "../../errors.js";
18
+ import type { QueryExecutor, TraceMetadata } from "../registry.js";
19
+ import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
20
+ import type { ApiRequestOptions } from "./types.js";
21
+
22
+ export type RestApiRequest = PartialMessage<RestApiIntegrationPlugin>;
23
+
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
+ export abstract class RestApiClientBase implements IntegrationClientImpl {
32
+ readonly name: string;
33
+ readonly pluginId: string;
34
+ readonly config: IntegrationConfig;
35
+
36
+ protected readonly executeQuery: QueryExecutor;
37
+
38
+ constructor(config: IntegrationConfig, executeQuery: QueryExecutor) {
39
+ this.name = config.name;
40
+ this.pluginId = config.pluginId;
41
+ this.config = config;
42
+ this.executeQuery = executeQuery;
43
+ }
44
+
45
+ /**
46
+ * Create a Property object for query params / headers.
47
+ */
48
+ protected createParam(key: string, value: unknown): PartialMessage<Property> {
49
+ return {
50
+ key,
51
+ value: typeof value === "string" ? value : JSON.stringify(value),
52
+ };
53
+ }
54
+
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
+ protected async executeApiRequest<TBody>(
67
+ options: ApiRequestOptions<TBody>,
68
+ bodySchema?: z.ZodSchema<TBody>,
69
+ metadata?: TraceMetadata,
70
+ ): Promise<unknown> {
71
+ // Validate request body if both body and schema are present.
72
+ if (options.body !== undefined && bodySchema) {
73
+ const bodyParseResult = bodySchema.safeParse(options.body);
74
+ if (!bodyParseResult.success) {
75
+ throw new RestApiValidationError(
76
+ `Request body validation failed: ${bodyParseResult.error.message}`,
77
+ {
78
+ zodError: bodyParseResult.error,
79
+ data: options.body,
80
+ },
81
+ );
82
+ }
83
+ }
84
+
85
+ const headers: PartialMessage<Property>[] = [];
86
+ if (options.headers) {
87
+ for (const [key, value] of Object.entries(options.headers)) {
88
+ headers.push(this.createParam(key, value));
89
+ }
90
+ }
91
+
92
+ const params: PartialMessage<Property>[] = [];
93
+ if (options.params) {
94
+ for (const [key, value] of Object.entries(options.params)) {
95
+ params.push(this.createParam(key, value));
96
+ }
97
+ }
98
+
99
+ const request: RestApiRequest = {
100
+ openApiAction: "genericHttpRequest",
101
+ httpMethod: options.method.toUpperCase(),
102
+ urlPath: options.path,
103
+ headers,
104
+ params,
105
+ responseType: "json",
106
+ };
107
+
108
+ if (options.body !== undefined) {
109
+ request.body = JSON.stringify(options.body);
110
+ request.bodyType = "jsonBody";
111
+ }
112
+
113
+ const result = await this.executeQuery(
114
+ request as Record<string, unknown>,
115
+ undefined,
116
+ metadata,
117
+ );
118
+
119
+ 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
+ }
130
+ }
131
+
132
+ return result;
133
+ }
134
+ }
@@ -1,58 +1,33 @@
1
1
  /**
2
- * Base client for REST API Integration (OpenAPI) based integrations.
2
+ * Generic REST API Integration client with Zod response validation.
3
3
  *
4
- * Provides common apiRequest() method for all OpenAPI-based plugins.
4
+ * Extends RestApiClientBase with an apiRequest() that validates the
5
+ * full response against a caller-supplied Zod schema and throws
6
+ * RestApiValidationError on mismatch.
5
7
  */
6
8
 
7
- import type { PartialMessage } from "@bufbuild/protobuf";
8
9
  import type { z } from "zod";
9
10
 
10
- import type { Property } from "@superblocksteam/types/dist/src/common/v1/plugin_pb";
11
- import type { Plugin as RestApiIntegrationPlugin } from "@superblocksteam/types/dist/src/plugins/restapiintegration/v1/plugin_pb";
12
-
13
11
  import { RestApiValidationError } from "../../errors.js";
14
- import type { QueryExecutor, TraceMetadata } from "../registry.js";
15
- import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
12
+ import type { TraceMetadata } from "../registry.js";
13
+ import { RestApiClientBase } from "./rest-api-client-base.js";
16
14
  import type {
17
15
  ApiRequestOptions,
18
16
  ApiRequestSchema,
19
17
  SupportsApiRequest,
20
18
  } from "./types.js";
21
19
 
22
- export type RestApiRequest = PartialMessage<RestApiIntegrationPlugin>;
23
-
24
20
  /**
25
21
  * Base implementation for REST API Integration clients.
26
22
  *
27
- * All OpenAPI-based integration clients extend this class to inherit
28
- * the generic apiRequest() method with runtime schema validation.
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.
29
26
  */
30
27
  export abstract class RestApiIntegrationClient
31
- implements IntegrationClientImpl, SupportsApiRequest
28
+ extends RestApiClientBase
29
+ implements SupportsApiRequest
32
30
  {
33
- readonly name: string;
34
- readonly pluginId: string;
35
- readonly config: IntegrationConfig;
36
-
37
- protected readonly executeQuery: QueryExecutor;
38
-
39
- constructor(config: IntegrationConfig, executeQuery: QueryExecutor) {
40
- this.name = config.name;
41
- this.pluginId = config.pluginId;
42
- this.config = config;
43
- this.executeQuery = executeQuery;
44
- }
45
-
46
- /**
47
- * Helper to create a Property object for params.
48
- */
49
- protected createParam(key: string, value: unknown): PartialMessage<Property> {
50
- return {
51
- key,
52
- value: typeof value === "string" ? value : JSON.stringify(value),
53
- };
54
- }
55
-
56
31
  async apiRequest<TBody, TResponse>(
57
32
  options: ApiRequestOptions<TBody>,
58
33
  schema: ApiRequestSchema<TBody, TResponse> & {
@@ -60,59 +35,7 @@ export abstract class RestApiIntegrationClient
60
35
  },
61
36
  metadata?: TraceMetadata,
62
37
  ): Promise<TResponse> {
63
- // Validate request body against schema if both are present.
64
- // Body schema is optional — if not provided, the body is sent unvalidated.
65
- if (options.body !== undefined && schema?.body) {
66
- const bodyParseResult = schema.body.safeParse(options.body);
67
- if (!bodyParseResult.success) {
68
- throw new RestApiValidationError(
69
- `Request body validation failed: ${bodyParseResult.error.message}`,
70
- {
71
- zodError: bodyParseResult.error,
72
- data: options.body,
73
- },
74
- );
75
- }
76
- }
77
-
78
- // Convert headers to Property array
79
- const headers: PartialMessage<Property>[] = [];
80
- if (options.headers) {
81
- for (const [key, value] of Object.entries(options.headers)) {
82
- headers.push(this.createParam(key, value));
83
- }
84
- }
85
-
86
- // Convert query params to Property array
87
- const params: PartialMessage<Property>[] = [];
88
- if (options.params) {
89
- for (const [key, value] of Object.entries(options.params)) {
90
- params.push(this.createParam(key, value));
91
- }
92
- }
93
-
94
- // Build the request using raw HTTP mode
95
- const request: RestApiRequest = {
96
- openApiAction: "genericHttpRequest",
97
- httpMethod: options.method.toUpperCase(),
98
- urlPath: options.path,
99
- headers,
100
- params,
101
- responseType: "json",
102
- };
103
-
104
- // Add body if provided
105
- if (options.body !== undefined) {
106
- request.body = JSON.stringify(options.body);
107
- request.bodyType = "jsonBody";
108
- }
109
-
110
- // Execute the request
111
- const result = await this.executeQuery(
112
- request as Record<string, unknown>,
113
- undefined,
114
- metadata,
115
- );
38
+ const result = await this.executeApiRequest(options, schema.body, metadata);
116
39
 
117
40
  // Response schema is REQUIRED - always validate
118
41
  const responseParseResult = schema.response.safeParse(result);
@@ -54,7 +54,7 @@ export default api({
54
54
  const result = await ctx.integrations.bitbucket.apiRequest(
55
55
  {
56
56
  method: "GET",
57
- path: `/2.0/repositories/${workspace}`,
57
+ path: `/repositories/${workspace}`,
58
58
  params: {
59
59
  pagelen: 50,
60
60
  },
@@ -78,7 +78,7 @@ export default api({
78
78
  const repo = await ctx.integrations.bitbucket.apiRequest(
79
79
  {
80
80
  method: "GET",
81
- path: `/2.0/repositories/${workspace}/${repoSlug}`,
81
+ path: `/repositories/${workspace}/${repoSlug}`,
82
82
  },
83
83
  { response: RepositorySchema },
84
84
  );
@@ -111,7 +111,7 @@ const PullRequestSchema = z.object({
111
111
  const pr = await ctx.integrations.bitbucket.apiRequest(
112
112
  {
113
113
  method: "POST",
114
- path: `/2.0/repositories/${workspace}/${repoSlug}/pullrequests`,
114
+ path: `/repositories/${workspace}/${repoSlug}/pullrequests`,
115
115
  body: {
116
116
  title: "Add new feature",
117
117
  description: "This PR adds...",
@@ -143,7 +143,7 @@ const ListPRsResponseSchema = z.object({
143
143
  const result = await ctx.integrations.bitbucket.apiRequest(
144
144
  {
145
145
  method: "GET",
146
- path: `/2.0/repositories/${workspace}/${repoSlug}/pullrequests`,
146
+ path: `/repositories/${workspace}/${repoSlug}/pullrequests`,
147
147
  params: {
148
148
  state: "OPEN", // OPEN, MERGED, DECLINED, SUPERSEDED
149
149
  pagelen: 25,
@@ -172,7 +172,7 @@ const MergeResponseSchema = z.object({
172
172
  const result = await ctx.integrations.bitbucket.apiRequest(
173
173
  {
174
174
  method: "POST",
175
- path: `/2.0/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/merge`,
175
+ path: `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/merge`,
176
176
  body: {
177
177
  type: "pullrequest",
178
178
  message: "Merge pull request #123",
@@ -209,7 +209,7 @@ const ListCommitsResponseSchema = z.object({
209
209
  const result = await ctx.integrations.bitbucket.apiRequest(
210
210
  {
211
211
  method: "GET",
212
- path: `/2.0/repositories/${workspace}/${repoSlug}/commits`,
212
+ path: `/repositories/${workspace}/${repoSlug}/commits`,
213
213
  params: {
214
214
  branch: "main",
215
215
  pagelen: 10,
@@ -232,7 +232,7 @@ const BranchSchema = z.object({
232
232
  const branch = await ctx.integrations.bitbucket.apiRequest(
233
233
  {
234
234
  method: "POST",
235
- path: `/2.0/repositories/${workspace}/${repoSlug}/refs/branches`,
235
+ path: `/repositories/${workspace}/${repoSlug}/refs/branches`,
236
236
  body: {
237
237
  name: "feature/new-feature",
238
238
  target: {
@@ -251,7 +251,7 @@ const branch = await ctx.integrations.bitbucket.apiRequest(
251
251
  const content = await ctx.integrations.bitbucket.apiRequest(
252
252
  {
253
253
  method: "GET",
254
- path: `/2.0/repositories/${workspace}/${repoSlug}/src/main/README.md`,
254
+ path: `/repositories/${workspace}/${repoSlug}/src/main/README.md`,
255
255
  },
256
256
  { response: z.string() },
257
257
  );
@@ -274,7 +274,7 @@ const CommentSchema = z.object({
274
274
  const comment = await ctx.integrations.bitbucket.apiRequest(
275
275
  {
276
276
  method: "POST",
277
- path: `/2.0/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/comments`,
277
+ path: `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/comments`,
278
278
  body: {
279
279
  content: {
280
280
  raw: "LGTM! :+1:",
@@ -300,20 +300,20 @@ await bitbucket.listRepos();
300
300
 
301
301
  // CORRECT - Use apiRequest
302
302
  await ctx.integrations.bitbucket.apiRequest(
303
- { method: "POST", path: `/2.0/repositories/${workspace}/${repo}/pullrequests`, body: { ... } },
303
+ { method: "POST", path: `/repositories/${workspace}/${repo}/pullrequests`, body: { ... } },
304
304
  { response: PullRequestSchema }
305
305
  );
306
306
  ```
307
307
 
308
308
  ### API Version in Path
309
309
 
310
- Always include `/2.0/` prefix:
310
+ The base URL already includes `/2.0`, so do not repeat it in paths:
311
311
 
312
312
  ```typescript
313
- // WRONG - Missing version
313
+ // CORRECT - No version prefix (base URL already has /2.0)
314
314
  const path = `/repositories/${workspace}/${repo}`;
315
315
 
316
- // CORRECT - Include version
316
+ // WRONG - Duplicates version from base URL
317
317
  const path = `/2.0/repositories/${workspace}/${repo}`;
318
318
  ```
319
319
 
@@ -323,10 +323,10 @@ Use workspace slug for API calls:
323
323
 
324
324
  ```typescript
325
325
  // Workspace slug (preferred)
326
- const path = `/2.0/repositories/myworkspace/myrepo`;
326
+ const path = `/repositories/myworkspace/myrepo`;
327
327
 
328
328
  // Can also be username for personal repos
329
- const path = `/2.0/repositories/myusername/myrepo`;
329
+ const path = `/repositories/myusername/myrepo`;
330
330
  ```
331
331
 
332
332
  ### Pagination
@@ -340,7 +340,7 @@ async function getAllPRs(
340
340
  repo: string,
341
341
  ) {
342
342
  const allPRs: PullRequest[] = [];
343
- let url = `/2.0/repositories/${workspace}/${repo}/pullrequests`;
343
+ let url = `/repositories/${workspace}/${repo}/pullrequests`;
344
344
 
345
345
  while (url) {
346
346
  const result = await ctx.integrations.bitbucket.apiRequest(
@@ -385,7 +385,7 @@ Branch names with slashes need encoding:
385
385
  ```typescript
386
386
  // Branch name: feature/my-feature
387
387
  const branchName = encodeURIComponent("feature/my-feature");
388
- const path = `/2.0/repositories/${workspace}/${repo}/src/${branchName}/file.txt`;
388
+ const path = `/repositories/${workspace}/${repo}/src/${branchName}/file.txt`;
389
389
  ```
390
390
 
391
391
  ### Raw File Content
@@ -397,7 +397,7 @@ File content endpoints return raw text, not JSON:
397
397
  const content = await ctx.integrations.bitbucket.apiRequest(
398
398
  {
399
399
  method: "GET",
400
- path: `/2.0/repositories/${workspace}/${repo}/src/main/file.txt`,
400
+ path: `/repositories/${workspace}/${repo}/src/main/file.txt`,
401
401
  },
402
402
  { response: z.string() }, // Not a JSON schema
403
403
  );
@@ -410,7 +410,7 @@ import { RestApiValidationError } from "@superblocksteam/sdk-api";
410
410
 
411
411
  try {
412
412
  const result = await ctx.integrations.bitbucket.apiRequest(
413
- { method: "GET", path: `/2.0/repositories/${workspace}/${repo}` },
413
+ { method: "GET", path: `/repositories/${workspace}/${repo}` },
414
414
  { response: RepositorySchema },
415
415
  );
416
416
  } catch (error) {