@zapier/zapier-sdk 0.13.9 → 0.15.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/api/client.d.ts.map +1 -1
  3. package/dist/api/client.js +45 -22
  4. package/dist/index.cjs +210 -123
  5. package/dist/index.mjs +210 -123
  6. package/dist/plugins/getAuthentication/index.js +1 -1
  7. package/dist/plugins/getAuthentication/index.test.js +1 -1
  8. package/dist/plugins/getProfile/index.d.ts.map +1 -1
  9. package/dist/plugins/getProfile/index.js +1 -1
  10. package/dist/plugins/listActions/index.js +1 -1
  11. package/dist/plugins/listActions/index.test.js +1 -1
  12. package/dist/plugins/listApps/index.js +2 -2
  13. package/dist/plugins/listApps/index.test.js +1 -1
  14. package/dist/plugins/listAuthentications/index.js +1 -1
  15. package/dist/plugins/listAuthentications/index.test.js +13 -13
  16. package/dist/plugins/listInputFieldChoices/index.d.ts +3 -1
  17. package/dist/plugins/listInputFieldChoices/index.d.ts.map +1 -1
  18. package/dist/plugins/listInputFieldChoices/index.js +42 -21
  19. package/dist/plugins/listInputFieldChoices/index.test.js +188 -11
  20. package/dist/plugins/listInputFields/index.d.ts.map +1 -1
  21. package/dist/plugins/listInputFields/index.js +11 -16
  22. package/dist/plugins/listInputFields/index.test.js +8 -6
  23. package/dist/plugins/manifest/index.js +2 -2
  24. package/dist/plugins/manifest/index.test.js +3 -3
  25. package/dist/plugins/runAction/index.js +2 -2
  26. package/dist/plugins/runAction/index.test.js +4 -4
  27. package/dist/sdk.test.js +1 -1
  28. package/dist/services/implementations.d.ts +63 -0
  29. package/dist/services/implementations.d.ts.map +1 -0
  30. package/dist/services/implementations.js +79 -0
  31. package/package.json +1 -1
@@ -234,7 +234,7 @@ describe("listAuthentications plugin", () => {
234
234
  it("should use search parameter when provided", async () => {
235
235
  const sdk = createTestSdk();
236
236
  await sdk.listAuthentications({ search: "workspace" });
237
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
237
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
238
238
  searchParams: expect.objectContaining({
239
239
  search: "workspace",
240
240
  }),
@@ -243,7 +243,7 @@ describe("listAuthentications plugin", () => {
243
243
  it("should use title as search when search not provided", async () => {
244
244
  const sdk = createTestSdk();
245
245
  await sdk.listAuthentications({ title: "My Slack Workspace" });
246
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
246
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
247
247
  searchParams: expect.objectContaining({
248
248
  search: "My Slack Workspace",
249
249
  }),
@@ -255,7 +255,7 @@ describe("listAuthentications plugin", () => {
255
255
  search: "explicit search",
256
256
  title: "My Title",
257
257
  });
258
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
258
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
259
259
  searchParams: expect.objectContaining({
260
260
  search: "explicit search",
261
261
  }),
@@ -264,7 +264,7 @@ describe("listAuthentications plugin", () => {
264
264
  it("should pass accountId filter to API", async () => {
265
265
  const sdk = createTestSdk();
266
266
  await sdk.listAuthentications({ accountId: "acc_123" });
267
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
267
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
268
268
  searchParams: expect.objectContaining({
269
269
  account_id: "acc_123",
270
270
  }),
@@ -273,7 +273,7 @@ describe("listAuthentications plugin", () => {
273
273
  it("should pass owner filter to API", async () => {
274
274
  const sdk = createTestSdk();
275
275
  await sdk.listAuthentications({ owner: "me" });
276
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
276
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
277
277
  searchParams: expect.objectContaining({
278
278
  owner: "me",
279
279
  }),
@@ -284,7 +284,7 @@ describe("listAuthentications plugin", () => {
284
284
  await sdk.listAuthentications({
285
285
  authenticationIds: ["123", "456", "789"],
286
286
  });
287
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
287
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
288
288
  searchParams: expect.objectContaining({
289
289
  ids: "123,456,789",
290
290
  }),
@@ -295,7 +295,7 @@ describe("listAuthentications plugin", () => {
295
295
  await sdk.listAuthentications({
296
296
  authenticationIds: [],
297
297
  });
298
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
298
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
299
299
  searchParams: expect.not.objectContaining({
300
300
  ids: expect.anything(),
301
301
  }),
@@ -394,7 +394,7 @@ describe("listAuthentications plugin", () => {
394
394
  it("should set appropriate pageSize for API calls", async () => {
395
395
  const sdk = createTestSdk();
396
396
  await sdk.listAuthentications({ pageSize: 1 });
397
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
397
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
398
398
  searchParams: expect.objectContaining({
399
399
  limit: "1",
400
400
  }),
@@ -405,12 +405,12 @@ describe("listAuthentications plugin", () => {
405
405
  it("should call the correct API endpoint", async () => {
406
406
  const sdk = createTestSdk();
407
407
  await sdk.listAuthentications({});
408
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.any(Object));
408
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.any(Object));
409
409
  });
410
410
  it("should pass pageSize as limit parameter", async () => {
411
411
  const sdk = createTestSdk();
412
412
  await sdk.listAuthentications({ pageSize: 25 });
413
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
413
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
414
414
  searchParams: expect.objectContaining({
415
415
  limit: "25",
416
416
  }),
@@ -440,7 +440,7 @@ describe("listAuthentications plugin", () => {
440
440
  expect(pageCount).toBe(2);
441
441
  expect(mockApiClient.get).toHaveBeenCalledTimes(2);
442
442
  // The second call should include the offset from the cursor
443
- expect(mockApiClient.get).toHaveBeenNthCalledWith(2, "/api/v4/authentications/", expect.objectContaining({
443
+ expect(mockApiClient.get).toHaveBeenNthCalledWith(2, "/zapier/api/v4/authentications/", expect.objectContaining({
444
444
  searchParams: expect.objectContaining({
445
445
  offset: "1",
446
446
  }),
@@ -500,7 +500,7 @@ describe("listAuthentications plugin", () => {
500
500
  const sdk = createTestSdk();
501
501
  await sdk.listAuthentications({ appKey: "slack" });
502
502
  // Should not add versionless_selected_api parameter
503
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
503
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
504
504
  searchParams: expect.not.objectContaining({
505
505
  versionless_selected_api: expect.any(String),
506
506
  }),
@@ -510,7 +510,7 @@ describe("listAuthentications plugin", () => {
510
510
  mockGetVersionedImplementationId.mockResolvedValue("SlackCLIAPI@2.1.3");
511
511
  const sdk = createTestSdk();
512
512
  await sdk.listAuthentications({ appKey: "slack" });
513
- expect(mockApiClient.get).toHaveBeenCalledWith("/api/v4/authentications/", expect.objectContaining({
513
+ expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
514
514
  searchParams: expect.objectContaining({
515
515
  versionless_selected_api: "SlackCLIAPI",
516
516
  }),
@@ -3,6 +3,7 @@ import type { ApiClient } from "../../api";
3
3
  import type { InputFieldChoiceItem } from "./schemas";
4
4
  import { ListInputFieldChoicesSchema, type ListInputFieldChoicesOptions } from "./schemas";
5
5
  import type { GetActionPluginProvides } from "../getAction";
6
+ import type { GetVersionedImplementationId } from "../manifest/schemas";
6
7
  export interface ListInputFieldChoicesPluginProvides {
7
8
  listInputFieldChoices: (options: ListInputFieldChoicesOptions) => Promise<{
8
9
  data: InputFieldChoiceItem[];
@@ -24,6 +25,7 @@ export interface ListInputFieldChoicesPluginProvides {
24
25
  export declare const listInputFieldChoicesPlugin: Plugin<GetSdkType<GetActionPluginProvides>, // requires getAction in SDK
25
26
  {
26
27
  api: ApiClient;
27
- }, // requires api in context
28
+ getVersionedImplementationId: GetVersionedImplementationId;
29
+ }, // requires api and getVersionedImplementationId in context
28
30
  ListInputFieldChoicesPluginProvides>;
29
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/listInputFieldChoices/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAM3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EACL,2BAA2B,EAC3B,KAAK,4BAA4B,EAGlC,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAsB5D,MAAM,WAAW,mCAAmC;IAClD,qBAAqB,EAAE,CAAC,OAAO,EAAE,4BAA4B,KAAK,OAAO,CAAC;QACxE,IAAI,EAAE,oBAAoB,EAAE,CAAC;QAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,GACA,aAAa,CAAC;QAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QACrE,KAAK,IAAI,aAAa,CAAC,oBAAoB,CAAC,CAAC;KAC9C,CAAC;IACJ,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,qBAAqB,EAAE;gBACrB,WAAW,EAAE,OAAO,2BAA2B,CAAC;aACjD,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAC9C,UAAU,CAAC,uBAAuB,CAAC,EAAE,4BAA4B;AACjE;IAAE,GAAG,EAAE,SAAS,CAAA;CAAE,EAAE,0BAA0B;AAC9C,mCAAmC,CAkHpC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/listInputFieldChoices/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,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EACL,2BAA2B,EAC3B,KAAK,4BAA4B,EAGlC,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AA0BxE,MAAM,WAAW,mCAAmC;IAClD,qBAAqB,EAAE,CAAC,OAAO,EAAE,4BAA4B,KAAK,OAAO,CAAC;QACxE,IAAI,EAAE,oBAAoB,EAAE,CAAC;QAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,GACA,aAAa,CAAC;QAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QACrE,KAAK,IAAI,aAAa,CAAC,oBAAoB,CAAC,CAAC;KAC9C,CAAC;IACJ,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,qBAAqB,EAAE;gBACrB,WAAW,EAAE,OAAO,2BAA2B,CAAC;aACjD,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAC9C,UAAU,CAAC,uBAAuB,CAAC,EAAE,4BAA4B;AACjE;IACE,GAAG,EAAE,SAAS,CAAC;IACf,4BAA4B,EAAE,4BAA4B,CAAC;CAC5D,EAAE,2DAA2D;AAC9D,mCAAmC,CAyIpC,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { ListInputFieldChoicesSchema, InputFieldChoiceItemSchema, } from "./schemas";
2
- import { ZapierApiError } from "../../types/errors";
2
+ import { ZapierApiError, ZapierConfigurationError } from "../../types/errors";
3
3
  import { createPaginatedFunction } from "../../utils/function-utils";
4
4
  import { appKeyResolver, actionTypeResolver, actionKeyResolver, authenticationIdResolver, inputFieldKeyResolver, inputsAllOptionalResolver, } from "../../resolvers";
5
+ import { fetchImplementationNeeds, fetchImplementationChoices, } from "../../services/implementations";
5
6
  // Transform NeedChoices to InputFieldChoiceItem
6
7
  function transformNeedChoicesToInputFieldChoiceItem(choice) {
7
8
  return {
@@ -13,32 +14,52 @@ function transformNeedChoicesToInputFieldChoiceItem(choice) {
13
14
  }
14
15
  export const listInputFieldChoicesPlugin = ({ context, sdk }) => {
15
16
  const listInputFieldChoices = createPaginatedFunction(async function listInputFieldChoicesPage(options) {
16
- const { api } = context;
17
+ const { api, getVersionedImplementationId } = context;
17
18
  // Extract parameters
18
- const { appKey, actionType, actionKey, inputFieldKey, authenticationId, inputs, page, cursor, } = options;
19
- // Use sdk.getAction to get the action ID
20
- const actionResult = await sdk.getAction({ appKey, actionType, actionKey });
21
- const actionId = actionResult.data.id;
22
- if (!actionId) {
19
+ const { appKey, actionType, actionKey, inputFieldKey, authenticationId = null, inputs, page, cursor, } = options;
20
+ // Get versioned implementation ID for the app
21
+ const selectedApi = await getVersionedImplementationId(appKey);
22
+ if (!selectedApi) {
23
+ throw new ZapierConfigurationError("No current_implementation_id found for app", { configType: "current_implementation_id" });
24
+ }
25
+ // Get action details
26
+ const { data: action } = await sdk.getAction({
27
+ appKey,
28
+ actionType,
29
+ actionKey,
30
+ });
31
+ // First, check for static choices by fetching implementation needs directly
32
+ const needsData = await fetchImplementationNeeds({
33
+ api,
34
+ selectedApi,
35
+ action: action.key,
36
+ actionType,
37
+ authenticationId,
38
+ inputs,
39
+ });
40
+ // Find the specific Need by key - search through raw needs array
41
+ const targetNeed = needsData.needs?.find((need) => need.key === inputFieldKey);
42
+ // If the need has static choices, return them
43
+ if (targetNeed?.choices && targetNeed.choices.length > 0) {
44
+ return {
45
+ data: targetNeed.choices.map(transformNeedChoicesToInputFieldChoiceItem),
46
+ nextCursor: undefined,
47
+ };
48
+ }
49
+ if (!action.id) {
23
50
  throw new ZapierApiError(`Action ${actionKey} does not have an ID - cannot retrieve input field choices`);
24
51
  }
25
- // Build choices request using action ID from getAction
26
52
  // Use cursor (from pagination) as page number if available, otherwise use explicit page or default to 0
27
53
  const requestPage = cursor ? parseInt(cursor, 10) : (page ?? 0);
28
- const choicesRequest = {
29
- action_id: actionId,
30
- input_field_id: inputFieldKey,
54
+ // Use service layer to fetch dynamic choices
55
+ const choicesData = await fetchImplementationChoices({
56
+ api,
57
+ actionId: action.id,
58
+ inputFieldId: inputFieldKey,
59
+ authenticationId,
60
+ inputs,
31
61
  page: requestPage,
32
- params: inputs || {},
33
- };
34
- // Only include authentication_id if it's not null (skip authentication when null)
35
- if (authenticationId !== null) {
36
- choicesRequest.authentication_id = authenticationId;
37
- }
38
- const choicesData = await api.post("/api/v4/implementations/choices/", choicesRequest);
39
- if (!choicesData.success) {
40
- throw new ZapierApiError(`Failed to get input field choices: ${choicesData.errors?.join(", ") || "Unknown error"}`);
41
- }
62
+ });
42
63
  // Transform NeedChoices objects to InputFieldChoiceItem objects
43
64
  const choices = (choicesData.choices || []).map(transformNeedChoicesToInputFieldChoiceItem);
44
65
  // Handle pagination
@@ -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 === "/zapier/api/v4/implementations/needs/") {
95
+ return Promise.resolve(mockNeedsResponse);
96
+ }
97
+ else if (path === "/zapier/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 === "/zapier/api/v4/implementations/needs/") {
223
+ return Promise.resolve(mockNeedsResponseWithStaticChoices);
224
+ }
225
+ else if (path === "/zapier/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("/zapier/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("/zapier/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 === "/zapier/api/v4/implementations/needs/") {
266
+ return Promise.resolve(mockNeedsResponse); // Has empty choices
267
+ }
268
+ else if (path === "/zapier/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("/zapier/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("/zapier/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 === "/zapier/api/v4/implementations/needs/") {
313
+ return Promise.resolve(mockNeedsWithOtherField);
314
+ }
315
+ else if (path === "/zapier/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("/zapier/api/v4/implementations/needs/", expect.anything());
330
+ // Should call choices endpoint since field was not found in needs
331
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/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();
@@ -169,7 +345,7 @@ describe("listInputFieldChoices plugin", () => {
169
345
  actionType: "read",
170
346
  actionKey: "send_message",
171
347
  });
172
- expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/choices/", {
348
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/api/v4/implementations/choices/", {
173
349
  action_id: "core:123",
174
350
  input_field_id: "channel",
175
351
  page: 0,
@@ -185,7 +361,7 @@ describe("listInputFieldChoices plugin", () => {
185
361
  inputFieldKey: "channel",
186
362
  authenticationId: 456,
187
363
  });
188
- expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/choices/", {
364
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/api/v4/implementations/choices/", {
189
365
  action_id: "core:123",
190
366
  input_field_id: "channel",
191
367
  authentication_id: 456,
@@ -202,7 +378,7 @@ describe("listInputFieldChoices plugin", () => {
202
378
  inputFieldKey: "channel",
203
379
  authenticationId: null,
204
380
  });
205
- expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/choices/", {
381
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/api/v4/implementations/choices/", {
206
382
  action_id: "core:123",
207
383
  input_field_id: "channel",
208
384
  page: 0,
@@ -220,7 +396,7 @@ describe("listInputFieldChoices plugin", () => {
220
396
  inputFieldKey: "channel",
221
397
  inputs,
222
398
  });
223
- expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/choices/", {
399
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/api/v4/implementations/choices/", {
224
400
  action_id: "core:123",
225
401
  input_field_id: "channel",
226
402
  page: 0,
@@ -236,7 +412,7 @@ describe("listInputFieldChoices plugin", () => {
236
412
  inputFieldKey: "channel",
237
413
  page: 2,
238
414
  });
239
- expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/choices/", {
415
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/api/v4/implementations/choices/", {
240
416
  action_id: "core:123",
241
417
  input_field_id: "channel",
242
418
  page: 2,
@@ -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 {
@@ -133,7 +133,7 @@ describe("listInputFields plugin", () => {
133
133
  actionType: "write",
134
134
  actionKey: "send_message",
135
135
  });
136
- expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/needs/", {
136
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/api/v4/implementations/needs/", {
137
137
  selected_api: "SlackCLIAPI@1.21.1",
138
138
  action: "send_message",
139
139
  type_of: "write",
@@ -148,7 +148,7 @@ describe("listInputFields plugin", () => {
148
148
  actionKey: "send_message",
149
149
  authenticationId: 123,
150
150
  });
151
- expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/needs/", {
151
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/api/v4/implementations/needs/", {
152
152
  selected_api: "SlackCLIAPI@1.21.1",
153
153
  action: "send_message",
154
154
  type_of: "write",
@@ -164,7 +164,7 @@ describe("listInputFields plugin", () => {
164
164
  actionKey: "send_message",
165
165
  authenticationId: null,
166
166
  });
167
- expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/needs/", {
167
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/api/v4/implementations/needs/", {
168
168
  selected_api: "SlackCLIAPI@1.21.1",
169
169
  action: "send_message",
170
170
  type_of: "write",
@@ -181,7 +181,7 @@ describe("listInputFields plugin", () => {
181
181
  actionKey: "send_message",
182
182
  inputs,
183
183
  });
184
- expect(mockApiClient.post).toHaveBeenCalledWith("/api/v4/implementations/needs/", {
184
+ expect(mockApiClient.post).toHaveBeenCalledWith("/zapier/api/v4/implementations/needs/", {
185
185
  selected_api: "SlackCLIAPI@1.21.1",
186
186
  action: "send_message",
187
187
  type_of: "write",
@@ -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