@zapier/zapier-sdk 0.13.1 → 0.13.3

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.
@@ -1,6 +1,10 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { ZapierValidationError, ZapierConfigurationError, ZapierApiError, } from "../../types/errors";
3
3
  import { listInputFieldsPlugin } from "./index";
4
+ import { getActionPlugin } from "../getAction";
5
+ import { getAppPlugin } from "../getApp";
6
+ import { listActionsPlugin } from "../listActions";
7
+ import { listAppsPlugin } from "../listApps";
4
8
  import { createSdk } from "../../sdk";
5
9
  const mockNeeds = [
6
10
  {
@@ -35,12 +39,34 @@ const mockNeedsResponse = {
35
39
  success: true,
36
40
  needs: mockNeeds,
37
41
  };
42
+ const mockActionsResponse = {
43
+ results: [
44
+ {
45
+ slug: "slack",
46
+ selected_api: "slack",
47
+ actions: [
48
+ {
49
+ key: "send_message",
50
+ name: "Send Message",
51
+ description: "Send a message to a channel",
52
+ type_of: "write",
53
+ type: "write",
54
+ id: "core:12345",
55
+ },
56
+ ],
57
+ },
58
+ ],
59
+ meta: {
60
+ next_cursor: null,
61
+ },
62
+ };
38
63
  describe("listInputFields plugin", () => {
39
64
  let mockApiClient;
40
65
  let mockGetVersionedImplementationId;
41
66
  beforeEach(() => {
42
67
  vi.clearAllMocks();
43
68
  mockApiClient = {
69
+ get: vi.fn().mockResolvedValue(mockActionsResponse),
44
70
  post: vi.fn().mockResolvedValue(mockNeedsResponse),
45
71
  };
46
72
  mockGetVersionedImplementationId = vi
@@ -52,7 +78,12 @@ describe("listInputFields plugin", () => {
52
78
  api: mockApiClient,
53
79
  meta: {},
54
80
  getVersionedImplementationId: mockGetVersionedImplementationId,
55
- }).addPlugin(listInputFieldsPlugin);
81
+ })
82
+ .addPlugin(listAppsPlugin)
83
+ .addPlugin(listActionsPlugin)
84
+ .addPlugin(getAppPlugin)
85
+ .addPlugin(getActionPlugin)
86
+ .addPlugin(listInputFieldsPlugin);
56
87
  }
57
88
  describe("schema validation", () => {
58
89
  it("should throw validation error for missing required fields", () => {
@@ -255,19 +286,19 @@ describe("listInputFields plugin", () => {
255
286
  it("should throw ZapierApiError when API response indicates failure", async () => {
256
287
  mockApiClient.post = vi.fn().mockResolvedValue({
257
288
  success: false,
258
- errors: ["Invalid action", "Missing parameter"],
289
+ errors: ["Authentication failed", "Invalid credentials"],
259
290
  });
260
291
  const sdk = createTestSdk();
261
292
  await expect(sdk.listInputFields({
262
293
  appKey: "slack",
263
294
  actionType: "write",
264
- actionKey: "invalid_action",
295
+ actionKey: "send_message", // Use valid action so we get to the POST call
265
296
  })).rejects.toThrow(ZapierApiError);
266
297
  await expect(sdk.listInputFields({
267
298
  appKey: "slack",
268
299
  actionType: "write",
269
- actionKey: "invalid_action",
270
- })).rejects.toThrow("Failed to get action fields: Invalid action, Missing parameter");
300
+ 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");
271
302
  });
272
303
  it("should handle API errors gracefully", async () => {
273
304
  mockApiClient.post = vi
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/runAction/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,EAEtB,MAAM,WAAW,CAAC;AAQnB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAQtD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC;QACjD,IAAI,EAAE,GAAG,EAAE,CAAC;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,GACA,aAAa,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QACpD,KAAK,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;KAC7B,CAAC;IACJ,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,SAAS,EAAE;gBACT,WAAW,EAAE,OAAO,eAAe,CAAC;aACrC,CAAC;SACH,CAAC;KACH,CAAC;CACH;AA4ED,eAAO,MAAM,eAAe,EAAE,MAAM,CAClC,UAAU,CAAC,uBAAuB,GAAG,oBAAoB,CAAC,EAAE,uCAAuC;AACnG;IACE,GAAG,EAAE,SAAS,CAAC;IACf,4BAA4B,EAAE,4BAA4B,CAAC;CAC5D,EAAE,0BAA0B;AAC7B,uBAAuB,CAmFxB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/runAction/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,EAEtB,MAAM,WAAW,CAAC;AAQnB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAQtD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC;QACjD,IAAI,EAAE,GAAG,EAAE,CAAC;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,GACA,aAAa,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QACpD,KAAK,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;KAC7B,CAAC;IACJ,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,SAAS,EAAE;gBACT,WAAW,EAAE,OAAO,eAAe,CAAC;aACrC,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAgFD,eAAO,MAAM,eAAe,EAAE,MAAM,CAClC,UAAU,CAAC,uBAAuB,GAAG,oBAAoB,CAAC,EAAE,uCAAuC;AACnG;IACE,GAAG,EAAE,SAAS,CAAC;IACf,4BAA4B,EAAE,4BAA4B,CAAC;CAC5D,EAAE,0BAA0B;AAC7B,uBAAuB,CAwFxB,CAAC"}
@@ -4,7 +4,7 @@ import { ZapierValidationError, ZapierConfigurationError, ZapierActionError, } f
4
4
  import { createPaginatedFunction } from "../../utils/function-utils";
5
5
  import { appKeyResolver, actionTypeResolver, actionKeyResolver, authenticationIdResolver, inputsResolver, } from "../../resolvers";
6
6
  async function executeAction(actionOptions) {
7
- const { api, context, appKey, actionKey, actionType, executionOptions, authenticationId, } = actionOptions;
7
+ const { api, context, appKey, actionId, actionKey, actionType, executionOptions, authenticationId, } = actionOptions;
8
8
  // Use the manifest plugin to get the current implementation ID
9
9
  const selectedApi = await context.getVersionedImplementationId(appKey);
10
10
  if (!selectedApi) {
@@ -13,6 +13,7 @@ async function executeAction(actionOptions) {
13
13
  // Step 1: POST to /actions/v1/runs to start execution
14
14
  const runRequestData = {
15
15
  selected_api: selectedApi,
16
+ action_id: actionId,
16
17
  action_key: actionKey,
17
18
  action_type: actionType,
18
19
  inputs: executionOptions.inputs || {},
@@ -47,11 +48,15 @@ export const runActionPlugin = ({ sdk, context }) => {
47
48
  if (actionData.data.action_type !== actionType) {
48
49
  throw new ZapierValidationError(`Action type mismatch: expected ${actionType}, got ${actionData.data.action_type}`);
49
50
  }
51
+ const actionId = actionData.data.id;
50
52
  // Execute the action using the Actions API (supports all action types)
51
53
  const result = await executeAction({
52
54
  api,
53
55
  context,
54
56
  appKey,
57
+ // Some actions require the action ID to run them, but technically the ID is not guaranteed to be available when
58
+ // we retrieve actions (probably legacy reasons), so we just pass along all the things!
59
+ actionId,
55
60
  actionKey,
56
61
  actionType,
57
62
  executionOptions: { inputs },
@@ -1 +1 @@
1
- {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/schemas/App.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAMtE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiBzB,CAAC;AAMF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/schemas/App.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAMtE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BzB,CAAC;AAMF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { withFormatter } from "../utils/schema-utils";
3
3
  import { ImplementationMetaSchema } from "../api/schemas";
4
+ import { toSnakeCase } from "../utils/string-utils";
4
5
  // ============================================================================
5
6
  // App Item Schema (extends ImplementationMetaSchema with transformed fields)
6
7
  // ============================================================================
@@ -10,10 +11,19 @@ export const AppItemSchema = withFormatter(ImplementationMetaSchema.omit({ name:
10
11
  implementation_id: z.string(), // Mapped from id (full versioned ID)
11
12
  }), {
12
13
  format: (item) => {
14
+ // Create additional keys if slug exists
15
+ const additionalKeys = [];
16
+ if (item.slug && item.slug !== item.key) {
17
+ additionalKeys.push(item.slug);
18
+ const snakeCaseSlug = toSnakeCase(item.slug);
19
+ if (snakeCaseSlug !== item.slug && snakeCaseSlug !== item.key) {
20
+ additionalKeys.push(snakeCaseSlug);
21
+ }
22
+ }
13
23
  return {
14
24
  title: item.title,
15
25
  key: item.key,
16
- keys: [item.slug, item.key].filter(Boolean),
26
+ keys: [item.key, ...additionalKeys],
17
27
  description: item.description,
18
28
  details: [],
19
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"domain-utils.d.ts","sourceRoot":"","sources":["../../src/utils/domain-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE/E;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GACnB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAQ9B;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,kBAAkB,EAAE,kBAAkB,GACrC,OAAO,CAgBT;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACvD,kBAAkB,CAqCpB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAsB9D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IAC9D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAgCA;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IACrD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAUA;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAY1D;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAI9C;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAkBvD;AAED,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,UAAU,GACrB,UAAU,IAAI,kBAAkB,CAElC;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,kBAAkB,GAAG,MAAM,CAEzE"}
1
+ {"version":3,"file":"domain-utils.d.ts","sourceRoot":"","sources":["../../src/utils/domain-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE/E;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GACnB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAQ9B;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,kBAAkB,EAAE,kBAAkB,GACrC,OAAO,CAYT;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACvD,kBAAkB,CAqCpB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAsB9D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IAC9D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAgCA;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IACrD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAUA;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAY1D;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAI9C;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAkBvD;AAED,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,UAAU,GACrB,UAAU,IAAI,kBAAkB,CAElC;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,kBAAkB,GAAG,MAAM,CAEzE"}
@@ -27,18 +27,14 @@ export function splitVersionedKey(versionedKey) {
27
27
  * @returns Normalized AppItem with essential fields only
28
28
  */
29
29
  export function normalizeImplementationMetaToAppItem(implementationMeta) {
30
- // Extract API name and version from the implementation ID
31
30
  const [selectedApi, appVersion] = splitVersionedKey(implementationMeta.id);
32
- // Destructure to exclude id and name before spreading
33
31
  const { id, name, ...restOfImplementationMeta } = implementationMeta;
34
32
  return {
35
- // Pass through all ImplementationMeta fields except id and name
36
33
  ...restOfImplementationMeta,
37
- // Transform key fields
38
34
  title: name,
39
35
  key: selectedApi,
40
- implementation_id: id, // Keep the full versioned ID
41
- version: appVersion, // Extract version separately
36
+ implementation_id: id,
37
+ version: appVersion,
42
38
  };
43
39
  }
44
40
  /**
@@ -9,4 +9,13 @@
9
9
  * - mixed formats: "first_name-value" → "First Name Value"
10
10
  */
11
11
  export declare function toTitleCase(input: string): string;
12
+ /**
13
+ * Converts a string to snake_case, handling various input formats:
14
+ * - camelCase: "firstName" → "first_name"
15
+ * - kebab-case: "first-name" → "first_name"
16
+ * - title case: "First Name" → "first_name"
17
+ * - mixed formats: "first-Name Value" → "first_name_value"
18
+ * - starts with number: "123abc" → "_123abc"
19
+ */
20
+ export declare function toSnakeCase(input: string): string;
12
21
  //# sourceMappingURL=string-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"string-utils.d.ts","sourceRoot":"","sources":["../../src/utils/string-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcjD"}
1
+ {"version":3,"file":"string-utils.d.ts","sourceRoot":"","sources":["../../src/utils/string-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcjD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBjD"}
@@ -21,3 +21,28 @@ export function toTitleCase(input) {
21
21
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
22
22
  .join(" "));
23
23
  }
24
+ /**
25
+ * Converts a string to snake_case, handling various input formats:
26
+ * - camelCase: "firstName" → "first_name"
27
+ * - kebab-case: "first-name" → "first_name"
28
+ * - title case: "First Name" → "first_name"
29
+ * - mixed formats: "first-Name Value" → "first_name_value"
30
+ * - starts with number: "123abc" → "_123abc"
31
+ */
32
+ export function toSnakeCase(input) {
33
+ let result = input
34
+ // insert underscore before capital letters (handles camelCase)
35
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
36
+ // replace spaces and dashes with underscores
37
+ .replace(/[\s\-]+/g, "_")
38
+ // replace multiple underscores with single underscore
39
+ .replace(/_+/g, "_")
40
+ // remove leading/trailing underscores and convert to lowercase
41
+ .replace(/^_|_$/g, "")
42
+ .toLowerCase();
43
+ // If the result starts with a number, prefix with underscore
44
+ if (/^[0-9]/.test(result)) {
45
+ result = "_" + result;
46
+ }
47
+ return result;
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "description": "Complete Zapier SDK - combines all Zapier SDK packages",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
package/src/api/debug.ts CHANGED
@@ -7,13 +7,56 @@
7
7
 
8
8
  import type { DebugLogger } from "./types";
9
9
 
10
+ import type { InspectOptions } from "util";
11
+
12
+ type UtilModule = {
13
+ inspect: (object: unknown, options?: InspectOptions) => string;
14
+ };
15
+
16
+ // We don't want to import util since it might not work in a browser. So we'll lazily load it and cache the result so
17
+ // we can use it synchronously after that. The types above are just to make our linter happy.
18
+ let utilModule: UtilModule | null = null;
19
+ let utilPromise: Promise<UtilModule | null> | null = null;
20
+
10
21
  export function createDebugLogger(enabled: boolean): DebugLogger {
11
22
  if (!enabled) {
12
23
  return () => {}; // No-op function when debug is disabled
13
24
  }
14
25
 
26
+ // Try to load util module on first use.
27
+ if (!utilPromise) {
28
+ utilPromise = import("util")
29
+ .then((util) => {
30
+ utilModule = util;
31
+ return util;
32
+ })
33
+ .catch(() => {
34
+ utilModule = null;
35
+ return null;
36
+ });
37
+ }
38
+
15
39
  return (message: string, data?: unknown) => {
16
- console.log(`[Zapier SDK] ${message}`, data || "");
40
+ if (data === undefined || data === "") {
41
+ console.log(`[Zapier SDK] ${message}`);
42
+ return;
43
+ }
44
+
45
+ // Use cached module if available for sync logging. Yes, this means if the util module isn't yet loaded, we'll fall
46
+ // back to a plain old console.log, which might mean we don't see proper formatting/nesting. But that's unlikely
47
+ // since this logging happens during async fetching. Alternatively, we could switch to async logging, but this
48
+ // seems fine since we have to have a fallback anyway.
49
+ if (utilModule) {
50
+ const formatted = utilModule.inspect(data, {
51
+ colors: true,
52
+ depth: null,
53
+ breakLength: 80,
54
+ });
55
+ console.log(`[Zapier SDK] ${message}`, formatted);
56
+ } else {
57
+ // Fallback - browser will handle formatting automatically
58
+ console.log(`[Zapier SDK] ${message}`, data);
59
+ }
17
60
  };
18
61
  }
19
62
 
package/src/index.ts CHANGED
@@ -42,6 +42,9 @@ export { isPositional, PositionalMetadata } from "./utils/schema-utils";
42
42
  export { createFunction } from "./utils/function-utils";
43
43
  export type { FormattedItem, FormatMetadata } from "./utils/schema-utils";
44
44
 
45
+ // Export string utilities
46
+ export { toSnakeCase, toTitleCase } from "./utils/string-utils";
47
+
45
48
  // Export resolver utilities for CLI
46
49
  export * from "./resolvers";
47
50
 
@@ -36,9 +36,12 @@ export const getActionPlugin: Plugin<
36
36
  // Use the listActions function from the SDK to search for the specific action
37
37
  const actionsResult = await sdk.listActions({ appKey });
38
38
 
39
- // Search through all actions to find the matching one
39
+ // Search through all actions to find the matching one (by key or ID)
40
40
  for (const action of actionsResult.data) {
41
- if (action.key === actionKey && action.action_type === actionType) {
41
+ if (
42
+ (action.key === actionKey || action.id === actionKey) &&
43
+ action.action_type === actionType
44
+ ) {
42
45
  return { data: action };
43
46
  }
44
47
  }
@@ -5,6 +5,10 @@ import {
5
5
  ZapierApiError,
6
6
  } from "../../types/errors";
7
7
  import { listInputFieldsPlugin } from "./index";
8
+ import { getActionPlugin } from "../getAction";
9
+ import { getAppPlugin } from "../getApp";
10
+ import { listActionsPlugin } from "../listActions";
11
+ import { listAppsPlugin } from "../listApps";
8
12
  import { createSdk } from "../../sdk";
9
13
  import type { ApiClient } from "../../api";
10
14
  import type { ListInputFieldsOptions } from "./schemas";
@@ -45,6 +49,28 @@ const mockNeedsResponse: NeedsResponse = {
45
49
  needs: mockNeeds,
46
50
  };
47
51
 
52
+ const mockActionsResponse = {
53
+ results: [
54
+ {
55
+ slug: "slack",
56
+ selected_api: "slack",
57
+ actions: [
58
+ {
59
+ key: "send_message",
60
+ name: "Send Message",
61
+ description: "Send a message to a channel",
62
+ type_of: "write",
63
+ type: "write",
64
+ id: "core:12345",
65
+ },
66
+ ],
67
+ },
68
+ ],
69
+ meta: {
70
+ next_cursor: null,
71
+ },
72
+ };
73
+
48
74
  describe("listInputFields plugin", () => {
49
75
  let mockApiClient: ApiClient;
50
76
  let mockGetVersionedImplementationId: any;
@@ -52,6 +78,7 @@ describe("listInputFields plugin", () => {
52
78
  beforeEach(() => {
53
79
  vi.clearAllMocks();
54
80
  mockApiClient = {
81
+ get: vi.fn().mockResolvedValue(mockActionsResponse),
55
82
  post: vi.fn().mockResolvedValue(mockNeedsResponse),
56
83
  } as Partial<ApiClient> as ApiClient;
57
84
 
@@ -69,7 +96,12 @@ describe("listInputFields plugin", () => {
69
96
  meta: {},
70
97
  getVersionedImplementationId: mockGetVersionedImplementationId,
71
98
  },
72
- ).addPlugin(listInputFieldsPlugin as any);
99
+ )
100
+ .addPlugin(listAppsPlugin as any)
101
+ .addPlugin(listActionsPlugin as any)
102
+ .addPlugin(getAppPlugin as any)
103
+ .addPlugin(getActionPlugin as any)
104
+ .addPlugin(listInputFieldsPlugin as any);
73
105
  }
74
106
 
75
107
  describe("schema validation", () => {
@@ -319,7 +351,7 @@ describe("listInputFields plugin", () => {
319
351
  it("should throw ZapierApiError when API response indicates failure", async () => {
320
352
  mockApiClient.post = vi.fn().mockResolvedValue({
321
353
  success: false,
322
- errors: ["Invalid action", "Missing parameter"],
354
+ errors: ["Authentication failed", "Invalid credentials"],
323
355
  });
324
356
 
325
357
  const sdk = createTestSdk();
@@ -327,7 +359,7 @@ describe("listInputFields plugin", () => {
327
359
  sdk.listInputFields({
328
360
  appKey: "slack",
329
361
  actionType: "write",
330
- actionKey: "invalid_action",
362
+ actionKey: "send_message", // Use valid action so we get to the POST call
331
363
  }),
332
364
  ).rejects.toThrow(ZapierApiError);
333
365
 
@@ -335,10 +367,10 @@ describe("listInputFields plugin", () => {
335
367
  sdk.listInputFields({
336
368
  appKey: "slack",
337
369
  actionType: "write",
338
- actionKey: "invalid_action",
370
+ actionKey: "send_message", // Use valid action so we get to the POST call
339
371
  }),
340
372
  ).rejects.toThrow(
341
- "Failed to get action fields: Invalid action, Missing parameter",
373
+ "Failed to get action fields: Authentication failed, Invalid credentials",
342
374
  );
343
375
  });
344
376
 
@@ -15,6 +15,7 @@ import {
15
15
  import { ZapierConfigurationError, ZapierApiError } from "../../types/errors";
16
16
  import { createPaginatedFunction } from "../../utils/function-utils";
17
17
  import type { GetAppPluginProvides } from "../getApp";
18
+ import type { GetActionPluginProvides } from "../getAction";
18
19
  import type { GetVersionedImplementationId } from "../manifest/schemas";
19
20
  import {
20
21
  appKeyResolver,
@@ -196,13 +197,13 @@ export interface ListInputFieldsPluginProvides {
196
197
  }
197
198
 
198
199
  export const listInputFieldsPlugin: Plugin<
199
- GetSdkType<GetAppPluginProvides>, // requires getApp in SDK
200
+ GetSdkType<GetAppPluginProvides & GetActionPluginProvides>, // requires getApp and getAction in SDK
200
201
  {
201
202
  api: ApiClient;
202
203
  getVersionedImplementationId: GetVersionedImplementationId;
203
204
  }, // requires api and getVersionedImplementationId in context
204
205
  ListInputFieldsPluginProvides
205
- > = ({ context }) => {
206
+ > = ({ sdk, context }) => {
206
207
  const listInputFields = createPaginatedFunction(
207
208
  async function listInputFieldsPage(
208
209
  options: ListInputFieldsOptions & { cursor?: string } & {
@@ -226,10 +227,16 @@ export const listInputFieldsPlugin: Plugin<
226
227
  );
227
228
  }
228
229
 
230
+ const { data: action } = await sdk.getAction({
231
+ appKey,
232
+ actionType,
233
+ actionKey,
234
+ });
235
+
229
236
  // Build needs request
230
237
  const needsRequest: NeedsRequest = {
231
238
  selected_api: selectedApi,
232
- action: actionKey,
239
+ action: action.key,
233
240
  type_of: actionType,
234
241
  params: inputs || {},
235
242
  };
@@ -46,6 +46,7 @@ interface ExecuteActionOptions {
46
46
  getVersionedImplementationId: GetVersionedImplementationId;
47
47
  };
48
48
  appKey: string;
49
+ actionId?: string;
49
50
  actionKey: string;
50
51
  actionType: string;
51
52
  executionOptions: { inputs: Record<string, unknown> };
@@ -61,6 +62,7 @@ async function executeAction(actionOptions: ExecuteActionOptions): Promise<{
61
62
  api,
62
63
  context,
63
64
  appKey,
65
+ actionId,
64
66
  actionKey,
65
67
  actionType,
66
68
  executionOptions,
@@ -80,12 +82,14 @@ async function executeAction(actionOptions: ExecuteActionOptions): Promise<{
80
82
  // Step 1: POST to /actions/v1/runs to start execution
81
83
  const runRequestData: {
82
84
  selected_api: string;
85
+ action_id?: string;
83
86
  action_key: string;
84
87
  action_type: string;
85
88
  inputs: Record<string, unknown>;
86
89
  authentication_id?: number | null;
87
90
  } = {
88
91
  selected_api: selectedApi,
92
+ action_id: actionId,
89
93
  action_key: actionKey,
90
94
  action_type: actionType,
91
95
  inputs: executionOptions.inputs || {},
@@ -148,11 +152,16 @@ export const runActionPlugin: Plugin<
148
152
  );
149
153
  }
150
154
 
155
+ const actionId = actionData.data.id;
156
+
151
157
  // Execute the action using the Actions API (supports all action types)
152
158
  const result = await executeAction({
153
159
  api,
154
160
  context,
155
161
  appKey,
162
+ // Some actions require the action ID to run them, but technically the ID is not guaranteed to be available when
163
+ // we retrieve actions (probably legacy reasons), so we just pass along all the things!
164
+ actionId,
156
165
  actionKey,
157
166
  actionType,
158
167
  executionOptions: { inputs },
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { withFormatter } from "../utils/schema-utils";
3
3
  import { ImplementationMetaSchema } from "../api/schemas";
4
+ import { toSnakeCase } from "../utils/string-utils";
4
5
 
5
6
  export { FormattedItem, FormatMetadata } from "../utils/schema-utils";
6
7
 
@@ -16,10 +17,20 @@ export const AppItemSchema = withFormatter(
16
17
  }),
17
18
  {
18
19
  format: (item) => {
20
+ // Create additional keys if slug exists
21
+ const additionalKeys = [];
22
+ if (item.slug && item.slug !== item.key) {
23
+ additionalKeys.push(item.slug);
24
+ const snakeCaseSlug = toSnakeCase(item.slug);
25
+ if (snakeCaseSlug !== item.slug && snakeCaseSlug !== item.key) {
26
+ additionalKeys.push(snakeCaseSlug);
27
+ }
28
+ }
29
+
19
30
  return {
20
31
  title: item.title,
21
32
  key: item.key,
22
- keys: [item.slug, item.key].filter(Boolean),
33
+ keys: [item.key, ...additionalKeys],
23
34
  description: item.description,
24
35
  details: [],
25
36
  };
@@ -36,20 +36,16 @@ export function splitVersionedKey(
36
36
  export function normalizeImplementationMetaToAppItem(
37
37
  implementationMeta: ImplementationMeta,
38
38
  ): AppItem {
39
- // Extract API name and version from the implementation ID
40
39
  const [selectedApi, appVersion] = splitVersionedKey(implementationMeta.id);
41
40
 
42
- // Destructure to exclude id and name before spreading
43
41
  const { id, name, ...restOfImplementationMeta } = implementationMeta;
44
42
 
45
43
  return {
46
- // Pass through all ImplementationMeta fields except id and name
47
44
  ...restOfImplementationMeta,
48
- // Transform key fields
49
45
  title: name,
50
46
  key: selectedApi,
51
- implementation_id: id, // Keep the full versioned ID
52
- version: appVersion, // Extract version separately
47
+ implementation_id: id,
48
+ version: appVersion,
53
49
  };
54
50
  }
55
51
 
@@ -24,3 +24,31 @@ export function toTitleCase(input: string): string {
24
24
  .join(" ")
25
25
  );
26
26
  }
27
+
28
+ /**
29
+ * Converts a string to snake_case, handling various input formats:
30
+ * - camelCase: "firstName" → "first_name"
31
+ * - kebab-case: "first-name" → "first_name"
32
+ * - title case: "First Name" → "first_name"
33
+ * - mixed formats: "first-Name Value" → "first_name_value"
34
+ * - starts with number: "123abc" → "_123abc"
35
+ */
36
+ export function toSnakeCase(input: string): string {
37
+ let result = input
38
+ // insert underscore before capital letters (handles camelCase)
39
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
40
+ // replace spaces and dashes with underscores
41
+ .replace(/[\s\-]+/g, "_")
42
+ // replace multiple underscores with single underscore
43
+ .replace(/_+/g, "_")
44
+ // remove leading/trailing underscores and convert to lowercase
45
+ .replace(/^_|_$/g, "")
46
+ .toLowerCase();
47
+
48
+ // If the result starts with a number, prefix with underscore
49
+ if (/^[0-9]/.test(result)) {
50
+ result = "_" + result;
51
+ }
52
+
53
+ return result;
54
+ }