@zapier/zapier-sdk 0.13.8 → 0.14.0

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.
@@ -41,17 +41,73 @@ const mockExternalChoicesResponse = {
41
41
  prev: null,
42
42
  },
43
43
  };
44
+ // Mock needs data for static choices
45
+ const mockNeedsWithStaticChoices = [
46
+ {
47
+ key: "priority",
48
+ label: "Priority",
49
+ help_text: "Select priority",
50
+ required: true,
51
+ type: "string",
52
+ choices: [
53
+ {
54
+ key: "high",
55
+ sample: "high",
56
+ label: "High Priority",
57
+ value: "high",
58
+ },
59
+ {
60
+ key: "medium",
61
+ sample: "medium",
62
+ label: "Medium Priority",
63
+ value: "medium",
64
+ },
65
+ ],
66
+ },
67
+ ];
68
+ const mockNeedsWithoutChoices = [
69
+ {
70
+ key: "channel",
71
+ label: "Channel",
72
+ help_text: "Select a channel",
73
+ required: true,
74
+ type: "string",
75
+ choices: [], // Empty choices
76
+ },
77
+ ];
78
+ const mockNeedsResponse = {
79
+ success: true,
80
+ needs: mockNeedsWithoutChoices,
81
+ };
82
+ const mockNeedsResponseWithStaticChoices = {
83
+ success: true,
84
+ needs: mockNeedsWithStaticChoices,
85
+ };
44
86
  describe("listInputFieldChoices plugin", () => {
45
87
  let mockApiClient;
46
88
  let mockGetAction;
89
+ let mockGetVersionedImplementationId;
47
90
  beforeEach(() => {
48
91
  vi.clearAllMocks();
92
+ // Mock api.post to handle both endpoints
93
+ const mockPost = vi.fn().mockImplementation((path) => {
94
+ if (path === "/api/v4/implementations/needs/") {
95
+ return Promise.resolve(mockNeedsResponse);
96
+ }
97
+ else if (path === "/api/v4/implementations/choices/") {
98
+ return Promise.resolve(mockChoicesResponse);
99
+ }
100
+ return Promise.reject(new Error(`Unexpected endpoint: ${path}`));
101
+ });
49
102
  mockApiClient = {
50
- post: vi.fn().mockResolvedValue(mockChoicesResponse),
103
+ post: mockPost,
51
104
  };
52
105
  mockGetAction = vi.fn().mockResolvedValue({
53
106
  data: { id: "core:123", key: "send_message", action_type: "read" },
54
107
  });
108
+ mockGetVersionedImplementationId = vi
109
+ .fn()
110
+ .mockResolvedValue("SlackCLIAPI@1.21.1");
55
111
  });
56
112
  function createTestSdk() {
57
113
  // Create a mock getAction plugin
@@ -66,7 +122,11 @@ describe("listInputFieldChoices plugin", () => {
66
122
  },
67
123
  });
68
124
  // Build SDK with proper plugin composition
69
- return createSdk({}, {}, { api: mockApiClient, meta: {} })
125
+ return createSdk({}, {}, {
126
+ api: mockApiClient,
127
+ meta: {},
128
+ getVersionedImplementationId: mockGetVersionedImplementationId,
129
+ })
70
130
  .addPlugin(mockGetActionPlugin)
71
131
  .addPlugin(listInputFieldChoicesPlugin);
72
132
  }
@@ -155,6 +215,122 @@ describe("listInputFieldChoices plugin", () => {
155
215
  }
156
216
  });
157
217
  });
218
+ describe("static choices from needs endpoint", () => {
219
+ it("should return static choices when field has them", async () => {
220
+ // Mock api.post to return needs with static choices
221
+ const mockPost = vi.fn().mockImplementation((path) => {
222
+ if (path === "/api/v4/implementations/needs/") {
223
+ return Promise.resolve(mockNeedsResponseWithStaticChoices);
224
+ }
225
+ else if (path === "/api/v4/implementations/choices/") {
226
+ return Promise.resolve(mockChoicesResponse);
227
+ }
228
+ return Promise.reject(new Error(`Unexpected endpoint: ${path}`));
229
+ });
230
+ mockApiClient.post = mockPost;
231
+ const sdk = createTestSdk();
232
+ const result = await sdk.listInputFieldChoices({
233
+ appKey: "slack",
234
+ actionType: "read",
235
+ actionKey: "send_message",
236
+ inputFieldKey: "priority",
237
+ });
238
+ // Should return static choices
239
+ expect(result.data).toHaveLength(2);
240
+ expect(result.data[0]).toEqual({
241
+ key: "high",
242
+ label: "High Priority",
243
+ sample: "high",
244
+ value: "high",
245
+ });
246
+ expect(result.data[1]).toEqual({
247
+ key: "medium",
248
+ label: "Medium Priority",
249
+ sample: "medium",
250
+ value: "medium",
251
+ });
252
+ // Should have called needs endpoint to check for static choices
253
+ expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/needs/", {
254
+ selected_api: "SlackCLIAPI@1.21.1",
255
+ action: "send_message",
256
+ type_of: "read",
257
+ params: {},
258
+ });
259
+ // Should not call choices endpoint since static choices were found
260
+ expect(mockApiClient.post).not.toHaveBeenCalledWith("/api/v4/implementations/choices/", expect.anything());
261
+ });
262
+ it("should fall back to dynamic choices when field has empty choices array", async () => {
263
+ // Mock api.post - needs endpoint returns field with empty choices
264
+ const mockPost = vi.fn().mockImplementation((path) => {
265
+ if (path === "/api/v4/implementations/needs/") {
266
+ return Promise.resolve(mockNeedsResponse); // Has empty choices
267
+ }
268
+ else if (path === "/api/v4/implementations/choices/") {
269
+ return Promise.resolve(mockChoicesResponse);
270
+ }
271
+ return Promise.reject(new Error(`Unexpected endpoint: ${path}`));
272
+ });
273
+ mockApiClient.post = mockPost;
274
+ const sdk = createTestSdk();
275
+ const result = await sdk.listInputFieldChoices({
276
+ appKey: "slack",
277
+ actionType: "read",
278
+ actionKey: "send_message",
279
+ inputFieldKey: "channel",
280
+ });
281
+ // Should first call needs endpoint
282
+ expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/needs/", {
283
+ selected_api: "SlackCLIAPI@1.21.1",
284
+ action: "send_message",
285
+ type_of: "read",
286
+ params: {},
287
+ });
288
+ // Should then call choices endpoint for dynamic choices
289
+ expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/choices/", expect.objectContaining({
290
+ action_id: "core:123",
291
+ input_field_id: "channel",
292
+ }));
293
+ // Should return dynamic choices
294
+ expect(result.data).toHaveLength(3);
295
+ });
296
+ it("should fall back to dynamic choices when field is not found", async () => {
297
+ // Mock api.post - needs endpoint returns different field
298
+ const mockNeedsWithOtherField = {
299
+ success: true,
300
+ needs: [
301
+ {
302
+ key: "other_field",
303
+ label: "Other Field",
304
+ help_text: "Some other field",
305
+ required: false,
306
+ type: "string",
307
+ choices: [],
308
+ },
309
+ ],
310
+ };
311
+ const mockPost = vi.fn().mockImplementation((path) => {
312
+ if (path === "/api/v4/implementations/needs/") {
313
+ return Promise.resolve(mockNeedsWithOtherField);
314
+ }
315
+ else if (path === "/api/v4/implementations/choices/") {
316
+ return Promise.resolve(mockChoicesResponse);
317
+ }
318
+ return Promise.reject(new Error(`Unexpected endpoint: ${path}`));
319
+ });
320
+ mockApiClient.post = mockPost;
321
+ const sdk = createTestSdk();
322
+ await sdk.listInputFieldChoices({
323
+ appKey: "slack",
324
+ actionType: "read",
325
+ actionKey: "send_message",
326
+ inputFieldKey: "channel",
327
+ });
328
+ // Should call needs endpoint first
329
+ expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/needs/", expect.anything());
330
+ // Should call choices endpoint since field was not found in needs
331
+ expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/choices/", expect.anything());
332
+ });
333
+ });
158
334
  describe("API integration - action method", () => {
159
335
  it("should call the correct API endpoint with action method", async () => {
160
336
  const sdk = createTestSdk();
@@ -439,12 +615,13 @@ describe("listInputFieldChoices plugin", () => {
439
615
  actionKey: "send_message",
440
616
  inputFieldKey: "invalid_field",
441
617
  })).rejects.toThrow(ZapierApiError);
618
+ // Error comes from needs endpoint (first call) with user-friendly message
442
619
  await expect(sdk.listInputFieldChoices({
443
620
  appKey: "slack",
444
621
  actionType: "read",
445
622
  actionKey: "send_message",
446
623
  inputFieldKey: "invalid_field",
447
- })).rejects.toThrow("Failed to get input field choices: Invalid field, Missing authentication");
624
+ })).rejects.toThrow("Failed to get input fields: Invalid field, Missing authentication");
448
625
  });
449
626
  it("should handle API errors gracefully", async () => {
450
627
  mockApiClient.post = vi
@@ -497,7 +674,7 @@ describe("listInputFieldChoices plugin", () => {
497
674
  actionType: "read",
498
675
  actionKey: "send_message",
499
676
  inputFieldKey: "channel",
500
- })).rejects.toThrow("Failed to get input field choices: Unknown error");
677
+ })).rejects.toThrow("Failed to get input fields: Unknown error");
501
678
  });
502
679
  it("should handle API response with errors but no error messages", async () => {
503
680
  mockApiClient.post = vi.fn().mockResolvedValue({
@@ -510,7 +687,7 @@ describe("listInputFieldChoices plugin", () => {
510
687
  actionType: "read",
511
688
  actionKey: "send_message",
512
689
  inputFieldKey: "channel",
513
- })).rejects.toThrow("Failed to get input field choices: Unknown error");
690
+ })).rejects.toThrow("Failed to get input fields: Unknown error");
514
691
  });
515
692
  it("should handle API response with empty errors array", async () => {
516
693
  mockApiClient.post = vi.fn().mockResolvedValue({
@@ -523,7 +700,7 @@ describe("listInputFieldChoices plugin", () => {
523
700
  actionType: "read",
524
701
  actionKey: "send_message",
525
702
  inputFieldKey: "channel",
526
- })).rejects.toThrow("Failed to get input field choices: Unknown error");
703
+ })).rejects.toThrow("Failed to get input fields: Unknown error");
527
704
  });
528
705
  });
529
706
  describe("context and metadata", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/listInputFields/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,qBAAqB,EACrB,KAAK,sBAAsB,EAE5B,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAmKxE,MAAM,WAAW,6BAA6B;IAC5C,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE,sBAAsB,KAAK,OAAO,CAAC;QAC7D,IAAI,EAAE,aAAa,EAAE,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,GACA,aAAa,CAAC;QAAE,IAAI,EAAE,aAAa,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QAC9D,KAAK,IAAI,aAAa,CAAC,cAAc,GAAG,aAAa,GAAG,YAAY,CAAC,CAAC;KACvE,CAAC;IACJ,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,eAAe,EAAE;gBACf,WAAW,EAAE,OAAO,qBAAqB,CAAC;aAC3C,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED,eAAO,MAAM,qBAAqB,EAAE,MAAM,CACxC,UAAU,CAAC,oBAAoB,GAAG,uBAAuB,CAAC,EAAE,uCAAuC;AACnG;IACE,GAAG,EAAE,SAAS,CAAC;IACf,4BAA4B,EAAE,4BAA4B,CAAC;CAC5D,EAAE,2DAA2D;AAC9D,6BAA6B,CAyF9B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/listInputFields/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,qBAAqB,EACrB,KAAK,sBAAsB,EAE5B,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAoKxE,MAAM,WAAW,6BAA6B;IAC5C,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE,sBAAsB,KAAK,OAAO,CAAC;QAC7D,IAAI,EAAE,aAAa,EAAE,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,GACA,aAAa,CAAC;QAAE,IAAI,EAAE,aAAa,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QAC9D,KAAK,IAAI,aAAa,CAAC,cAAc,GAAG,aAAa,GAAG,YAAY,CAAC,CAAC;KACvE,CAAC;IACJ,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,eAAe,EAAE;gBACf,WAAW,EAAE,OAAO,qBAAqB,CAAC;aAC3C,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED,eAAO,MAAM,qBAAqB,EAAE,MAAM,CACxC,UAAU,CAAC,oBAAoB,GAAG,uBAAuB,CAAC,EAAE,uCAAuC;AACnG;IACE,GAAG,EAAE,SAAS,CAAC;IACf,4BAA4B,EAAE,4BAA4B,CAAC;CAC5D,EAAE,2DAA2D;AAC9D,6BAA6B,CA8E9B,CAAC"}
@@ -1,9 +1,10 @@
1
1
  import { ListInputFieldsSchema, } from "./schemas";
2
- import { ZapierConfigurationError, ZapierApiError } from "../../types/errors";
2
+ import { ZapierConfigurationError } from "../../types/errors";
3
3
  import { createPaginatedFunction } from "../../utils/function-utils";
4
4
  import { appKeyResolver, actionTypeResolver, actionKeyResolver, authenticationIdResolver, inputsAllOptionalResolver, } from "../../resolvers";
5
5
  import { RootFieldItemSchema } from "../../schemas/Field";
6
6
  import { toTitleCase } from "../../utils/string-utils";
7
+ import { fetchImplementationNeeds } from "../../services/implementations";
7
8
  // Enums for input field transformation
8
9
  var InputFieldType;
9
10
  (function (InputFieldType) {
@@ -147,7 +148,7 @@ export const listInputFieldsPlugin = ({ sdk, context }) => {
147
148
  // Note: This function ignores pageSize and cursor since it's not actually paginated internally
148
149
  const { api, getVersionedImplementationId } = context;
149
150
  // Extract parameters
150
- const { appKey, actionKey, actionType, authenticationId, inputs } = options;
151
+ const { appKey, actionKey, actionType, authenticationId = null, inputs, } = options;
151
152
  // Use the manifest plugin
152
153
  const selectedApi = await getVersionedImplementationId(appKey);
153
154
  if (!selectedApi) {
@@ -158,21 +159,15 @@ export const listInputFieldsPlugin = ({ sdk, context }) => {
158
159
  actionType,
159
160
  actionKey,
160
161
  });
161
- // Build needs request
162
- const needsRequest = {
163
- selected_api: selectedApi,
162
+ // Use service layer to fetch implementation needs
163
+ const needsData = await fetchImplementationNeeds({
164
+ api,
165
+ selectedApi,
164
166
  action: action.key,
165
- type_of: actionType,
166
- params: inputs || {},
167
- };
168
- // Only include authentication_id if it's not null (skip authentication when null)
169
- if (authenticationId !== null) {
170
- needsRequest.authentication_id = authenticationId;
171
- }
172
- const needsData = await api.post("/api/v4/implementations/needs/", needsRequest);
173
- if (!needsData.success) {
174
- throw new ZapierApiError(`Failed to get action fields: ${needsData.errors?.join(", ") || "Unknown error"}`);
175
- }
167
+ actionType,
168
+ authenticationId,
169
+ inputs,
170
+ });
176
171
  // Transform Need objects to Root Fieldset with proper nesting
177
172
  const rootFieldset = transformNeedsToFields(needsData.needs || []);
178
173
  return {
@@ -208,7 +208,7 @@ describe("listInputFields plugin", () => {
208
208
  placeholder: "Enter your message",
209
209
  default_value: "",
210
210
  }));
211
- // Check channel field with SELECT format
211
+ // Check channel field with SELECT format (choices should NOT be exposed)
212
212
  expect(result.data[1]).toEqual(expect.objectContaining({
213
213
  key: "channel",
214
214
  title: "Channel",
@@ -217,6 +217,8 @@ describe("listInputFields plugin", () => {
217
217
  value_type: "STRING",
218
218
  format: "SELECT",
219
219
  }));
220
+ // Verify choices are NOT exposed in listInputFields output
221
+ expect(result.data[1]).not.toHaveProperty("choices");
220
222
  // Check array field
221
223
  expect(result.data[2]).toEqual(expect.objectContaining({
222
224
  key: "tags",
@@ -298,7 +300,7 @@ describe("listInputFields plugin", () => {
298
300
  appKey: "slack",
299
301
  actionType: "write",
300
302
  actionKey: "send_message", // Use valid action so we get to the POST call
301
- })).rejects.toThrow("Failed to get action fields: Authentication failed, Invalid credentials");
303
+ })).rejects.toThrow("Failed to get input fields: Authentication failed, Invalid credentials");
302
304
  });
303
305
  it("should handle API errors gracefully", async () => {
304
306
  mockApiClient.post = vi
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Implementation Services
3
+ *
4
+ * This module provides domain-specific service functions for interacting with
5
+ * the /api/v4/implementations/* endpoints. These services handle request building,
6
+ * validation, error handling, and authentication for implementation-related API calls.
7
+ *
8
+ * Services are consumed by plugins to avoid code duplication while maintaining
9
+ * separation between the generic HTTP client layer and business logic.
10
+ */
11
+ import type { ApiClient } from "../api";
12
+ import type { NeedsResponse, NeedChoicesResponse } from "../api/types";
13
+ /**
14
+ * Fetches implementation needs (input fields) for a specific action.
15
+ *
16
+ * This service calls the /api/v4/implementations/needs/ endpoint and returns
17
+ * the raw response after validating success. The response includes all field
18
+ * metadata including static choices, field types, and validation rules.
19
+ *
20
+ * @param params - Request parameters
21
+ * @param params.api - API client instance
22
+ * @param params.selectedApi - Versioned implementation ID (e.g., "SlackCLIAPI@1.21.1")
23
+ * @param params.action - Action key
24
+ * @param params.actionType - Action type (read, write, etc.)
25
+ * @param params.authenticationId - Authentication ID (null to skip authentication)
26
+ * @param params.inputs - Input values that may affect available fields
27
+ * @returns Promise resolving to NeedsResponse
28
+ * @throws {ZapierApiError} When the API request fails or returns success: false
29
+ */
30
+ export declare function fetchImplementationNeeds({ api, selectedApi, action, actionType, authenticationId, inputs, }: {
31
+ api: ApiClient;
32
+ selectedApi: string;
33
+ action: string;
34
+ actionType: string;
35
+ authenticationId: number | null;
36
+ inputs?: Record<string, unknown>;
37
+ }): Promise<NeedsResponse>;
38
+ /**
39
+ * Fetches dynamic choices for a specific input field.
40
+ *
41
+ * This service calls the /api/v4/implementations/choices/ endpoint and returns
42
+ * the raw response after validating success. The response includes available
43
+ * choices for dropdown fields with pagination support.
44
+ *
45
+ * @param params - Request parameters
46
+ * @param params.api - API client instance
47
+ * @param params.actionId - Action ID (e.g., "core:123")
48
+ * @param params.inputFieldId - Input field key
49
+ * @param params.authenticationId - Authentication ID (null to skip authentication)
50
+ * @param params.inputs - Input values that may affect available choices
51
+ * @param params.page - Page number for pagination (0-indexed)
52
+ * @returns Promise resolving to NeedChoicesResponse
53
+ * @throws {ZapierApiError} When the API request fails or returns success: false
54
+ */
55
+ export declare function fetchImplementationChoices({ api, actionId, inputFieldId, authenticationId, inputs, page, }: {
56
+ api: ApiClient;
57
+ actionId: string;
58
+ inputFieldId: string;
59
+ authenticationId: number | null;
60
+ inputs?: Record<string, unknown>;
61
+ page: number;
62
+ }): Promise<NeedChoicesResponse>;
63
+ //# sourceMappingURL=implementations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"implementations.d.ts","sourceRoot":"","sources":["../../src/services/implementations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAEV,aAAa,EAEb,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAGtB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,wBAAwB,CAAC,EAC7C,GAAG,EACH,WAAW,EACX,MAAM,EACN,UAAU,EACV,gBAAgB,EAChB,MAAM,GACP,EAAE;IACD,GAAG,EAAE,SAAS,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,GAAG,OAAO,CAAC,aAAa,CAAC,CAyBzB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,0BAA0B,CAAC,EAC/C,GAAG,EACH,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,MAAM,EACN,IAAI,GACL,EAAE;IACD,GAAG,EAAE,SAAS,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAyB/B"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Implementation Services
3
+ *
4
+ * This module provides domain-specific service functions for interacting with
5
+ * the /api/v4/implementations/* endpoints. These services handle request building,
6
+ * validation, error handling, and authentication for implementation-related API calls.
7
+ *
8
+ * Services are consumed by plugins to avoid code duplication while maintaining
9
+ * separation between the generic HTTP client layer and business logic.
10
+ */
11
+ import { ZapierApiError } from "../types/errors";
12
+ /**
13
+ * Fetches implementation needs (input fields) for a specific action.
14
+ *
15
+ * This service calls the /api/v4/implementations/needs/ endpoint and returns
16
+ * the raw response after validating success. The response includes all field
17
+ * metadata including static choices, field types, and validation rules.
18
+ *
19
+ * @param params - Request parameters
20
+ * @param params.api - API client instance
21
+ * @param params.selectedApi - Versioned implementation ID (e.g., "SlackCLIAPI@1.21.1")
22
+ * @param params.action - Action key
23
+ * @param params.actionType - Action type (read, write, etc.)
24
+ * @param params.authenticationId - Authentication ID (null to skip authentication)
25
+ * @param params.inputs - Input values that may affect available fields
26
+ * @returns Promise resolving to NeedsResponse
27
+ * @throws {ZapierApiError} When the API request fails or returns success: false
28
+ */
29
+ export async function fetchImplementationNeeds({ api, selectedApi, action, actionType, authenticationId, inputs, }) {
30
+ const request = {
31
+ selected_api: selectedApi,
32
+ action,
33
+ type_of: actionType,
34
+ params: inputs || {},
35
+ };
36
+ // Only include authentication_id if it's not null (skip authentication when null)
37
+ if (authenticationId !== null) {
38
+ request.authentication_id = authenticationId;
39
+ }
40
+ const response = await api.post("/api/v4/implementations/needs/", request);
41
+ if (!response.success) {
42
+ throw new ZapierApiError(`Failed to get input fields: ${response.errors?.join(", ") || "Unknown error"}`);
43
+ }
44
+ return response;
45
+ }
46
+ /**
47
+ * Fetches dynamic choices for a specific input field.
48
+ *
49
+ * This service calls the /api/v4/implementations/choices/ endpoint and returns
50
+ * the raw response after validating success. The response includes available
51
+ * choices for dropdown fields with pagination support.
52
+ *
53
+ * @param params - Request parameters
54
+ * @param params.api - API client instance
55
+ * @param params.actionId - Action ID (e.g., "core:123")
56
+ * @param params.inputFieldId - Input field key
57
+ * @param params.authenticationId - Authentication ID (null to skip authentication)
58
+ * @param params.inputs - Input values that may affect available choices
59
+ * @param params.page - Page number for pagination (0-indexed)
60
+ * @returns Promise resolving to NeedChoicesResponse
61
+ * @throws {ZapierApiError} When the API request fails or returns success: false
62
+ */
63
+ export async function fetchImplementationChoices({ api, actionId, inputFieldId, authenticationId, inputs, page, }) {
64
+ const request = {
65
+ action_id: actionId,
66
+ input_field_id: inputFieldId,
67
+ page,
68
+ params: inputs || {},
69
+ };
70
+ // Only include authentication_id if it's not null (skip authentication when null)
71
+ if (authenticationId !== null) {
72
+ request.authentication_id = authenticationId;
73
+ }
74
+ const response = await api.post("/api/v4/implementations/choices/", request);
75
+ if (!response.success) {
76
+ throw new ZapierApiError(`Failed to get input field choices: ${response.errors?.join(", ") || "Unknown error"}`);
77
+ }
78
+ return response;
79
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Batch Operation Utilities
3
+ *
4
+ * This module provides utilities for executing multiple async operations
5
+ * with concurrency control and retry logic.
6
+ */
7
+ /**
8
+ * Options for batch execution
9
+ */
10
+ export interface BatchOptions {
11
+ /**
12
+ * Maximum number of concurrent operations (default: 10)
13
+ * Lower values are more "polite" but slower
14
+ * Higher values are faster but risk rate limits
15
+ */
16
+ concurrency?: number;
17
+ /**
18
+ * Whether to retry failed operations (default: true)
19
+ * When enabled, each operation gets up to MAX_CONSECUTIVE_ERRORS retries
20
+ */
21
+ retry?: boolean;
22
+ /**
23
+ * Delay in milliseconds between starting batches (default: 100ms)
24
+ * Adds a small delay between concurrent batches to avoid burst patterns
25
+ */
26
+ batchDelay?: number;
27
+ /**
28
+ * Overall timeout for the entire batch operation in milliseconds (default: 180000ms / 3 minutes)
29
+ * When exceeded, throws ZapierTimeoutError and stops processing remaining tasks
30
+ */
31
+ timeoutMs?: number;
32
+ /**
33
+ * Timeout for individual tasks in milliseconds (default: none)
34
+ * When set, each task will be cancelled if it exceeds this duration
35
+ * Tasks that timeout will be marked as rejected in the results
36
+ */
37
+ taskTimeoutMs?: number;
38
+ }
39
+ /**
40
+ * Execute multiple async operations with concurrency limiting and retry logic
41
+ *
42
+ * This prevents overwhelming APIs by:
43
+ * 1. Limiting concurrent operations (worker pool pattern)
44
+ * 2. Adding small delays between batches to avoid burst detection
45
+ * 3. Retrying failed operations with exponential backoff
46
+ *
47
+ * Problem Solved:
48
+ * - Rate limit prevention: Steady stream instead of burst requests
49
+ * - Connection pool management: Stays within browser/Node limits (~6-8 per domain)
50
+ * - Resilience: Transient failures are retried automatically
51
+ * - Observability: Returns detailed success/failure info for each operation
52
+ *
53
+ * Example Usage:
54
+ * ```typescript
55
+ * // Instead of Promise.allSettled (fires all at once):
56
+ * const results = await Promise.allSettled(
57
+ * actions.map(a => sdk.listInputFields(a))
58
+ * );
59
+ *
60
+ * // Use batch (controlled concurrency):
61
+ * const results = await batch(
62
+ * actions.map(a => () => sdk.listInputFields(a)),
63
+ * { concurrency: 10, retry: true }
64
+ * );
65
+ * ```
66
+ *
67
+ * @param tasks - Array of functions that return promises (NOT promises themselves!)
68
+ * @param options - Configuration for concurrency and retry behavior
69
+ * @returns Promise resolving to array of settled results (same as Promise.allSettled)
70
+ */
71
+ export declare function batch<T>(tasks: (() => Promise<T>)[], options?: BatchOptions): Promise<PromiseSettledResult<T>[]>;
72
+ //# sourceMappingURL=batch-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-utils.d.ts","sourceRoot":"","sources":["../../src/utils/batch-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwBH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,KAAK,CAAC,CAAC,EAC3B,KAAK,EAAE,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,EAC3B,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC,CAuIpC"}