@zapier/zapier-sdk 0.10.0 → 0.11.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 (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/dist/index.cjs +130 -15
  4. package/dist/index.d.mts +121 -15
  5. package/dist/index.mjs +130 -15
  6. package/dist/plugins/listInputFields/index.d.ts +4 -4
  7. package/dist/plugins/listInputFields/index.d.ts.map +1 -1
  8. package/dist/plugins/listInputFields/index.js +61 -7
  9. package/dist/plugins/listInputFields/schemas.d.ts +3 -3
  10. package/dist/plugins/listInputFields/schemas.d.ts.map +1 -1
  11. package/dist/resolvers/inputFieldKey.d.ts.map +1 -1
  12. package/dist/resolvers/inputFieldKey.js +25 -1
  13. package/dist/resolvers/inputs.d.ts +12 -8
  14. package/dist/resolvers/inputs.d.ts.map +1 -1
  15. package/dist/resolvers/inputs.js +23 -4
  16. package/dist/schemas/Field.d.ts +109 -1
  17. package/dist/schemas/Field.d.ts.map +1 -1
  18. package/dist/schemas/Field.js +41 -2
  19. package/dist/types/domain.d.ts +17 -1
  20. package/dist/types/domain.d.ts.map +1 -1
  21. package/dist/utils/string-utils.d.ts +12 -0
  22. package/dist/utils/string-utils.d.ts.map +1 -0
  23. package/dist/utils/string-utils.js +23 -0
  24. package/dist/utils/string-utils.test.d.ts +2 -0
  25. package/dist/utils/string-utils.test.d.ts.map +1 -0
  26. package/dist/utils/string-utils.test.js +36 -0
  27. package/package.json +1 -1
  28. package/src/plugins/listInputFields/index.ts +73 -13
  29. package/src/plugins/listInputFields/schemas.ts +6 -3
  30. package/src/resolvers/inputFieldKey.ts +33 -1
  31. package/src/resolvers/inputs.ts +38 -12
  32. package/src/schemas/Field.ts +75 -5
  33. package/src/types/domain.ts +24 -1
  34. package/src/utils/string-utils.test.ts +45 -0
  35. package/src/utils/string-utils.ts +26 -0
  36. package/tsconfig.tsbuildinfo +1 -1
@@ -1,7 +1,12 @@
1
1
  import type { Plugin, GetSdkType } from "../../types/plugin";
2
2
  import type { ApiClient } from "../../api";
3
3
  import type { NeedsRequest, NeedsResponse, Need } from "../../api/types";
4
- import type { InputFieldItem } from "../../types/domain";
4
+ import type {
5
+ InputFieldItem,
6
+ InfoFieldItem,
7
+ FieldsetItem,
8
+ RootFieldItem,
9
+ } from "../../types/domain";
5
10
  import {
6
11
  ListInputFieldsSchema,
7
12
  type ListInputFieldsOptions,
@@ -18,7 +23,8 @@ import {
18
23
  authenticationIdResolver,
19
24
  inputsAllOptionalResolver,
20
25
  } from "../../resolvers";
21
- import { InputFieldItemSchema } from "../../schemas/Field";
26
+ import { RootFieldItemSchema } from "../../schemas/Field";
27
+ import { toTitleCase } from "../../utils/string-utils";
22
28
 
23
29
  // Enums for input field transformation
24
30
  enum InputFieldType {
@@ -101,7 +107,7 @@ function transformNeedToInputFieldItem(need: Need): InputFieldItem {
101
107
  const itemsType = getItemsTypeFromNeed(need);
102
108
 
103
109
  return {
104
- // Only the final computed/mapped fields that are in InputFieldItemSchema
110
+ type: "input_field",
105
111
  key: need.key,
106
112
  default_value: need.default || "",
107
113
  depends_on: need.depends_on || [],
@@ -116,12 +122,68 @@ function transformNeedToInputFieldItem(need: Need): InputFieldItem {
116
122
  };
117
123
  }
118
124
 
125
+ // Transform Need to InfoFieldItem (for copy fields)
126
+ function transformNeedToInfoFieldItem(need: Need): InfoFieldItem {
127
+ return {
128
+ type: "info_field",
129
+ key: need.key,
130
+ description: need.help_text || "",
131
+ title: need.label,
132
+ };
133
+ }
134
+
135
+ // Transform needs to fields (similar to partnerapi compute_root_fieldset_from_needs)
136
+ function transformNeedsToFields(needs: Need[]): RootFieldItem[] {
137
+ const rootFields: (InputFieldItem | InfoFieldItem | FieldsetItem)[] = [];
138
+ const fieldsetMap = new Map<string, FieldsetItem>();
139
+
140
+ for (const need of needs) {
141
+ if (need.computed) {
142
+ // Computed needs (i.e zap_id for CodeAPI steps) are not shown by the
143
+ // editor, hide them here too.
144
+ continue;
145
+ }
146
+
147
+ // If parent_key is set, we need to ensure that a corresponding
148
+ // fieldset exists, since we'll be adding this need to that fieldset
149
+ if (need.parent_key) {
150
+ let fieldset = fieldsetMap.get(need.parent_key);
151
+ if (!fieldset) {
152
+ fieldset = {
153
+ type: "fieldset",
154
+ key: need.parent_key,
155
+ title: toTitleCase(need.parent_key),
156
+ fields: [],
157
+ };
158
+ fieldsetMap.set(need.parent_key, fieldset);
159
+ rootFields.push(fieldset);
160
+ }
161
+
162
+ // Add field to fieldset
163
+ if (need.type === "copy" && need.help_text) {
164
+ fieldset.fields.push(transformNeedToInfoFieldItem(need));
165
+ } else {
166
+ fieldset.fields.push(transformNeedToInputFieldItem(need));
167
+ }
168
+ } else {
169
+ // Add field to root
170
+ if (need.type === "copy" && need.help_text) {
171
+ rootFields.push(transformNeedToInfoFieldItem(need));
172
+ } else {
173
+ rootFields.push(transformNeedToInputFieldItem(need));
174
+ }
175
+ }
176
+ }
177
+
178
+ return rootFields;
179
+ }
180
+
119
181
  export interface ListInputFieldsPluginProvides {
120
182
  listInputFields: (options?: ListInputFieldsOptions) => Promise<{
121
- data: InputFieldItem[];
183
+ data: RootFieldItem[];
122
184
  }> &
123
- AsyncIterable<{ data: InputFieldItem[]; nextCursor?: string }> & {
124
- items(): AsyncIterable<InputFieldItem>;
185
+ AsyncIterable<{ data: RootFieldItem[]; nextCursor?: string }> & {
186
+ items(): AsyncIterable<InputFieldItem | InfoFieldItem | FieldsetItem>;
125
187
  };
126
188
  context: {
127
189
  meta: {
@@ -189,13 +251,11 @@ export const listInputFieldsPlugin: Plugin<
189
251
  );
190
252
  }
191
253
 
192
- // Transform Need objects to InputFieldItem objects
193
- const inputFields: InputFieldItem[] = (needsData.needs || []).map(
194
- transformNeedToInputFieldItem,
195
- );
254
+ // Transform Need objects to Root Fieldset with proper nesting
255
+ const rootFieldset = transformNeedsToFields(needsData.needs || []);
196
256
 
197
257
  return {
198
- data: inputFields,
258
+ data: rootFieldset,
199
259
  nextCursor: undefined, // No pagination needed since we return all input fields
200
260
  };
201
261
  },
@@ -209,9 +269,9 @@ export const listInputFieldsPlugin: Plugin<
209
269
  listInputFields: {
210
270
  categories: ["action"],
211
271
  type: "list",
212
- itemType: "InputField",
272
+ itemType: "RootFieldItem",
213
273
  inputSchema: ListInputFieldsSchema,
214
- outputSchema: InputFieldItemSchema,
274
+ outputSchema: RootFieldItemSchema,
215
275
  resolvers: {
216
276
  appKey: appKeyResolver,
217
277
  actionType: actionTypeResolver,
@@ -6,7 +6,7 @@ import {
6
6
  AuthenticationIdPropertySchema,
7
7
  InputsPropertySchema,
8
8
  } from "../../types/properties";
9
- import type { InputFieldItem } from "../../types/domain";
9
+ import type { RootFieldItem } from "../../types/domain";
10
10
  import type { PaginatedSdkFunction } from "../../types/functions";
11
11
  import type {
12
12
  ZapierConfigurationError,
@@ -54,11 +54,14 @@ export type ListInputFieldsError =
54
54
 
55
55
  // Page result structure
56
56
  export interface ListInputFieldsPage {
57
- data: InputFieldItem[];
57
+ data: RootFieldItem[];
58
58
  nextCursor?: string;
59
59
  }
60
60
 
61
61
  // SDK function interface
62
62
  export interface ListInputFieldsSdkFunction {
63
- listInputFields: PaginatedSdkFunction<ListInputFieldsOptions, InputFieldItem>;
63
+ listInputFields: PaginatedSdkFunction<
64
+ ListInputFieldsOptions,
65
+ RootFieldItem[]
66
+ >;
64
67
  }
@@ -1,6 +1,37 @@
1
1
  import type { DynamicResolver } from "../utils/schema-utils";
2
2
  import type { InputFieldItem } from "../schemas/Field";
3
3
  import type { ActionTypeProperty } from "../types/properties";
4
+ import type {
5
+ RootFieldItem,
6
+ InfoFieldItem,
7
+ FieldsetItem,
8
+ } from "../types/domain";
9
+
10
+ /**
11
+ * Helper function to flatten fieldsets into a flat array of input fields.
12
+ * Used to extract all input fields from nested fieldset structure for field selection.
13
+ */
14
+ function flattenRootFieldset(rootFieldset: RootFieldItem[]): InputFieldItem[] {
15
+ const result: InputFieldItem[] = [];
16
+
17
+ function processItem(item: InputFieldItem | InfoFieldItem | FieldsetItem) {
18
+ if (item.type === "input_field") {
19
+ result.push(item);
20
+ } else if (item.type === "fieldset") {
21
+ // Recursively process fields in the fieldset
22
+ for (const field of item.fields) {
23
+ processItem(field);
24
+ }
25
+ }
26
+ // Skip info fields as they're not input fields
27
+ }
28
+
29
+ for (const item of rootFieldset) {
30
+ processItem(item);
31
+ }
32
+
33
+ return result;
34
+ }
4
35
 
5
36
  export const inputFieldKeyResolver: DynamicResolver<
6
37
  InputFieldItem,
@@ -22,7 +53,8 @@ export const inputFieldKeyResolver: DynamicResolver<
22
53
  authenticationId: resolvedParams.authenticationId,
23
54
  inputs: resolvedParams.inputs, // Pass along currently resolved inputs
24
55
  });
25
- return fieldsResponse.data;
56
+ // Flatten the fieldset structure for field selection
57
+ return flattenRootFieldset(fieldsResponse.data);
26
58
  },
27
59
  prompt: (fields) => ({
28
60
  type: "list",
@@ -1,13 +1,41 @@
1
- import type { FieldsResolver } from "../utils/schema-utils";
2
1
  import type { ActionTypeProperty } from "../types/properties";
2
+ import type { RootFieldItem } from "../types/domain";
3
3
 
4
- type InputsResolver = FieldsResolver<{
5
- appKey: string;
6
- actionKey: string;
7
- actionType: ActionTypeProperty;
8
- authenticationId: number;
9
- inputs?: Record<string, unknown>;
10
- }>;
4
+ // Helper function to recursively make all input fields optional while preserving structure
5
+ function makeFieldsOptional(fields: RootFieldItem[]): RootFieldItem[] {
6
+ return fields.map((field) => {
7
+ if (field.type === "input_field") {
8
+ return {
9
+ ...field,
10
+ is_required: false,
11
+ };
12
+ } else if (field.type === "fieldset") {
13
+ return {
14
+ ...field,
15
+ fields: makeFieldsOptional(field.fields),
16
+ };
17
+ } else {
18
+ // Info fields remain unchanged
19
+ return field;
20
+ }
21
+ });
22
+ }
23
+
24
+ // Custom resolver type for fieldsets - returns the nested structure
25
+ type InputsResolver = {
26
+ type: "fields";
27
+ depends: readonly string[];
28
+ fetch: (
29
+ sdk: any,
30
+ resolvedParams: {
31
+ appKey: string;
32
+ actionKey: string;
33
+ actionType: ActionTypeProperty;
34
+ authenticationId: number;
35
+ inputs?: Record<string, unknown>;
36
+ },
37
+ ) => Promise<RootFieldItem[]>;
38
+ };
11
39
 
12
40
  export const inputsResolver: InputsResolver = {
13
41
  type: "fields",
@@ -35,9 +63,7 @@ export const inputsAllOptionalResolver: InputsResolver = {
35
63
  authenticationId: resolvedParams.authenticationId,
36
64
  inputs: resolvedParams.inputs, // Pass along currently resolved inputs
37
65
  });
38
- return fieldsResponse.data.map((field) => ({
39
- ...field,
40
- is_required: false,
41
- }));
66
+ // Return nested structure with all fields marked as optional.
67
+ return makeFieldsOptional(fieldsResponse.data);
42
68
  },
43
69
  };
@@ -1,13 +1,22 @@
1
1
  import { z } from "zod";
2
2
  import { withFormatter } from "../utils/schema-utils";
3
3
 
4
+ // ============================================================================
5
+ // Base Field Schema
6
+ // ============================================================================
7
+
8
+ export const BaseFieldItemSchema = z.object({
9
+ type: z.string(), // "input_field", "info_field", or "fieldset"
10
+ key: z.string(), // From need.key
11
+ });
12
+
4
13
  // ============================================================================
5
14
  // Input Field Item Schema (extends API Need schema with computed fields and formatting)
6
15
  // ============================================================================
7
16
 
8
17
  export const InputFieldItemSchema = withFormatter(
9
- z.object({
10
- key: z.string(), // From need.key
18
+ BaseFieldItemSchema.extend({
19
+ type: z.literal("input_field"),
11
20
  default_value: z.string(), // Mapped from 'default' with fallback to ""
12
21
  depends_on: z.array(z.string()), // Mapped from 'depends_on' with fallback to []
13
22
  description: z.string(), // Mapped from 'help_text' with fallback to ""
@@ -89,11 +98,72 @@ export const InputFieldItemSchema = withFormatter(
89
98
  },
90
99
  );
91
100
 
101
+ // ============================================================================
102
+ // Info Field Item Schema (for help text and copy fields)
103
+ // ============================================================================
104
+
105
+ export const InfoFieldItemSchema = withFormatter(
106
+ BaseFieldItemSchema.extend({
107
+ type: z.literal("info_field"),
108
+ description: z.string(), // From need.help_text
109
+ title: z.string().optional(), // Optional title
110
+ }),
111
+ {
112
+ format: (item) => ({
113
+ title: item.title || "Info",
114
+ key: item.key,
115
+ description: item.description,
116
+ details: [{ text: item.description, style: "normal" as const }],
117
+ }),
118
+ },
119
+ );
120
+
121
+ // ============================================================================
122
+ // Fieldset Schema (for grouping fields)
123
+ // ============================================================================
124
+
125
+ // Forward reference for recursive type
126
+ type FieldsetItemType =
127
+ | z.infer<typeof InputFieldItemSchema>
128
+ | z.infer<typeof InfoFieldItemSchema>
129
+ | FieldsetItem;
130
+
131
+ export interface FieldsetItem {
132
+ type: "fieldset";
133
+ key: string;
134
+ title: string;
135
+ fields: FieldsetItemType[];
136
+ }
137
+
138
+ export const FieldsetItemSchema: z.ZodType<FieldsetItem> =
139
+ BaseFieldItemSchema.extend({
140
+ type: z.literal("fieldset"),
141
+ title: z.string(),
142
+ fields: z.lazy(() =>
143
+ z.array(
144
+ z.union([
145
+ InputFieldItemSchema,
146
+ InfoFieldItemSchema,
147
+ FieldsetItemSchema,
148
+ ]),
149
+ ),
150
+ ),
151
+ });
152
+
153
+ // ============================================================================
154
+ // Root Field Item Schema (union of all possible field types)
155
+ // ============================================================================
156
+
157
+ export const RootFieldItemSchema = z.union([
158
+ InputFieldItemSchema,
159
+ InfoFieldItemSchema,
160
+ FieldsetItemSchema,
161
+ ]);
162
+
92
163
  // ============================================================================
93
164
  // Type Exports
94
165
  // ============================================================================
95
166
 
96
167
  export type InputFieldItem = z.infer<typeof InputFieldItemSchema>;
97
-
98
- // Legacy alias for backward compatibility
99
- export type FieldItem = InputFieldItem;
168
+ export type InfoFieldItem = z.infer<typeof InfoFieldItemSchema>;
169
+ export type RootFieldItem = z.infer<typeof RootFieldItemSchema>;
@@ -9,8 +9,13 @@ import type { z } from "zod";
9
9
  import type { AppItemSchema } from "../schemas/App";
10
10
  import type { AuthenticationItemSchema } from "../schemas/Auth";
11
11
  import type { ActionItemSchema } from "../schemas/Action";
12
- import type { InputFieldItemSchema } from "../schemas/Field";
12
+ import type {
13
+ InputFieldItemSchema,
14
+ InfoFieldItemSchema,
15
+ RootFieldItemSchema,
16
+ } from "../schemas/Field";
13
17
  import type { UserProfileItemSchema } from "../schemas/UserProfile";
18
+ import type { FieldsetItem } from "../schemas/Field";
14
19
 
15
20
  /**
16
21
  * Represents an app item returned by getApp and listApps functions
@@ -36,6 +41,24 @@ export type ActionItem = z.infer<typeof ActionItemSchema>;
36
41
  */
37
42
  export type InputFieldItem = z.infer<typeof InputFieldItemSchema>;
38
43
 
44
+ /**
45
+ * Represents an info field item (help text/copy) returned by listInputFields functions
46
+ * Inferred from InfoFieldItemSchema
47
+ */
48
+ export type InfoFieldItem = z.infer<typeof InfoFieldItemSchema>;
49
+
50
+ /**
51
+ * Represents a fieldset (group of fields) returned by listInputFields functions
52
+ * This is defined as an interface rather than inferred due to recursive nature
53
+ */
54
+ export type { FieldsetItem };
55
+
56
+ /**
57
+ * Represents a root field item (any field/fieldset type) returned by listInputFields
58
+ * Inferred from RootFieldItemSchema - use as RootFieldItem[]
59
+ */
60
+ export type RootFieldItem = z.infer<typeof RootFieldItemSchema>;
61
+
39
62
  /**
40
63
  * Represents a user profile item returned by getProfile function
41
64
  * Inferred from UserProfileItemSchema which extends the base UserProfile API schema
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { toTitleCase } from "./string-utils";
3
+
4
+ describe("toTitleCase", () => {
5
+ it("converts snake_case to title case", () => {
6
+ expect(toTitleCase("first_name")).toBe("First Name");
7
+ expect(toTitleCase("sender_settings")).toBe("Sender Settings");
8
+ expect(toTitleCase("api_key_config")).toBe("Api Key Config");
9
+ });
10
+
11
+ it("converts camelCase to title case", () => {
12
+ expect(toTitleCase("firstName")).toBe("First Name");
13
+ expect(toTitleCase("senderSettings")).toBe("Sender Settings");
14
+ expect(toTitleCase("apiKeyConfig")).toBe("Api Key Config");
15
+ });
16
+
17
+ it("converts kebab-case to title case", () => {
18
+ expect(toTitleCase("first-name")).toBe("First Name");
19
+ expect(toTitleCase("sender-settings")).toBe("Sender Settings");
20
+ expect(toTitleCase("api-key-config")).toBe("Api Key Config");
21
+ });
22
+
23
+ it("handles mixed formats", () => {
24
+ expect(toTitleCase("first_name-value")).toBe("First Name Value");
25
+ expect(toTitleCase("sender_settings-config")).toBe(
26
+ "Sender Settings Config",
27
+ );
28
+ });
29
+
30
+ it("handles single words", () => {
31
+ expect(toTitleCase("name")).toBe("Name");
32
+ expect(toTitleCase("settings")).toBe("Settings");
33
+ });
34
+
35
+ it("handles multiple spaces and trims", () => {
36
+ expect(toTitleCase(" first name ")).toBe("First Name");
37
+ expect(toTitleCase("sender__settings")).toBe("Sender Settings");
38
+ });
39
+
40
+ it("handles empty and edge cases", () => {
41
+ expect(toTitleCase("")).toBe("");
42
+ expect(toTitleCase("a")).toBe("A");
43
+ expect(toTitleCase("_")).toBe("");
44
+ });
45
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * String utility functions for the Zapier SDK
3
+ */
4
+
5
+ /**
6
+ * Converts a string to title case, handling various input formats:
7
+ * - camelCase: "firstName" → "First Name"
8
+ * - snake_case: "first_name" → "First Name"
9
+ * - kebab-case: "first-name" → "First Name"
10
+ * - mixed formats: "first_name-value" → "First Name Value"
11
+ */
12
+ export function toTitleCase(input: string): string {
13
+ return (
14
+ input
15
+ // insert a space before capital letters (handles camelCase)
16
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
17
+ // replace delimiters (underscore, dash, multiple spaces) with single space
18
+ .replace(/[_\-]+/g, " ")
19
+ .replace(/\s+/g, " ")
20
+ .trim()
21
+ // split and capitalize each word
22
+ .split(" ")
23
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
24
+ .join(" ")
25
+ );
26
+ }