@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.
- package/CHANGELOG.md +14 -0
- package/dist/api/polling.d.ts.map +1 -1
- package/dist/api/polling.js +1 -11
- package/dist/index.cjs +219 -53
- package/dist/index.d.mts +73 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.mjs +219 -54
- package/dist/plugins/listInputFieldChoices/index.d.ts +3 -1
- package/dist/plugins/listInputFieldChoices/index.d.ts.map +1 -1
- package/dist/plugins/listInputFieldChoices/index.js +42 -21
- package/dist/plugins/listInputFieldChoices/index.test.js +183 -6
- package/dist/plugins/listInputFields/index.d.ts.map +1 -1
- package/dist/plugins/listInputFields/index.js +11 -16
- package/dist/plugins/listInputFields/index.test.js +4 -2
- package/dist/services/implementations.d.ts +63 -0
- package/dist/services/implementations.d.ts.map +1 -0
- package/dist/services/implementations.js +79 -0
- package/dist/utils/batch-utils.d.ts +72 -0
- package/dist/utils/batch-utils.d.ts.map +1 -0
- package/dist/utils/batch-utils.js +162 -0
- package/dist/utils/batch-utils.test.d.ts +2 -0
- package/dist/utils/batch-utils.test.d.ts.map +1 -0
- package/dist/utils/batch-utils.test.js +476 -0
- package/dist/utils/retry-utils.d.ts +45 -0
- package/dist/utils/retry-utils.d.ts.map +1 -0
- package/dist/utils/retry-utils.js +51 -0
- package/dist/utils/retry-utils.test.d.ts +2 -0
- package/dist/utils/retry-utils.test.d.ts.map +1 -0
- package/dist/utils/retry-utils.test.js +90 -0
- package/package.json +1 -1
|
@@ -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:
|
|
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({}, {}, {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
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
|
|
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
|
-
//
|
|
162
|
-
const
|
|
163
|
-
|
|
162
|
+
// Use service layer to fetch implementation needs
|
|
163
|
+
const needsData = await fetchImplementationNeeds({
|
|
164
|
+
api,
|
|
165
|
+
selectedApi,
|
|
164
166
|
action: action.key,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
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"}
|