@zapier/zapier-sdk 0.8.2 → 0.9.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 (104) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +10 -33
  3. package/dist/api/client.d.ts.map +1 -1
  4. package/dist/api/client.js +1 -2
  5. package/dist/api/polling.d.ts +36 -6
  6. package/dist/api/polling.d.ts.map +1 -1
  7. package/dist/api/polling.js +132 -28
  8. package/dist/api/polling.test.d.ts +2 -0
  9. package/dist/api/polling.test.d.ts.map +1 -0
  10. package/dist/api/polling.test.js +318 -0
  11. package/dist/api/types.d.ts +1 -2
  12. package/dist/api/types.d.ts.map +1 -1
  13. package/dist/index.cjs +489 -252
  14. package/dist/index.d.mts +182 -187
  15. package/dist/index.d.ts +1 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +0 -1
  18. package/dist/index.mjs +486 -251
  19. package/dist/plugins/apps/index.d.ts +4 -0
  20. package/dist/plugins/apps/index.d.ts.map +1 -1
  21. package/dist/plugins/getApp/index.d.ts +2 -7
  22. package/dist/plugins/getApp/index.d.ts.map +1 -1
  23. package/dist/plugins/getApp/index.js +9 -9
  24. package/dist/plugins/getApp/index.test.js +1 -1
  25. package/dist/plugins/getAuthentication/index.test.js +1 -1
  26. package/dist/plugins/listActions/index.d.ts +2 -4
  27. package/dist/plugins/listActions/index.d.ts.map +1 -1
  28. package/dist/plugins/listActions/index.js +1 -1
  29. package/dist/plugins/listActions/index.test.js +4 -4
  30. package/dist/plugins/listApps/index.d.ts +4 -7
  31. package/dist/plugins/listApps/index.d.ts.map +1 -1
  32. package/dist/plugins/listApps/index.js +33 -17
  33. package/dist/plugins/listApps/index.test.js +22 -2
  34. package/dist/plugins/listAuthentications/index.d.ts +2 -4
  35. package/dist/plugins/listAuthentications/index.d.ts.map +1 -1
  36. package/dist/plugins/listAuthentications/index.js +4 -0
  37. package/dist/plugins/listAuthentications/index.test.js +39 -13
  38. package/dist/plugins/listAuthentications/schemas.d.ts +3 -0
  39. package/dist/plugins/listAuthentications/schemas.d.ts.map +1 -1
  40. package/dist/plugins/listAuthentications/schemas.js +4 -0
  41. package/dist/plugins/manifest/index.d.ts +25 -9
  42. package/dist/plugins/manifest/index.d.ts.map +1 -1
  43. package/dist/plugins/manifest/index.js +239 -67
  44. package/dist/plugins/manifest/index.test.js +426 -171
  45. package/dist/plugins/manifest/schemas.d.ts +5 -1
  46. package/dist/plugins/manifest/schemas.d.ts.map +1 -1
  47. package/dist/plugins/manifest/schemas.js +1 -0
  48. package/dist/sdk.d.ts +5 -11
  49. package/dist/sdk.d.ts.map +1 -1
  50. package/dist/sdk.js +1 -4
  51. package/dist/types/plugin.d.ts +1 -0
  52. package/dist/types/plugin.d.ts.map +1 -1
  53. package/dist/types/sdk.d.ts +6 -3
  54. package/dist/types/sdk.d.ts.map +1 -1
  55. package/dist/utils/domain-utils.d.ts +16 -0
  56. package/dist/utils/domain-utils.d.ts.map +1 -1
  57. package/dist/utils/domain-utils.js +46 -27
  58. package/dist/utils/domain-utils.test.js +157 -3
  59. package/dist/utils/file-utils.d.ts +4 -0
  60. package/dist/utils/file-utils.d.ts.map +1 -0
  61. package/dist/utils/file-utils.js +74 -0
  62. package/dist/utils/file-utils.test.d.ts +2 -0
  63. package/dist/utils/file-utils.test.d.ts.map +1 -0
  64. package/dist/utils/file-utils.test.js +51 -0
  65. package/package.json +1 -1
  66. package/src/api/client.ts +5 -4
  67. package/src/api/polling.test.ts +405 -0
  68. package/src/api/polling.ts +224 -44
  69. package/src/api/types.ts +1 -2
  70. package/src/index.ts +1 -1
  71. package/src/plugins/apps/index.ts +9 -2
  72. package/src/plugins/getApp/index.test.ts +1 -1
  73. package/src/plugins/getApp/index.ts +12 -14
  74. package/src/plugins/getAuthentication/index.test.ts +1 -1
  75. package/src/plugins/listActions/index.test.ts +8 -7
  76. package/src/plugins/listActions/index.ts +3 -3
  77. package/src/plugins/listApps/index.test.ts +23 -2
  78. package/src/plugins/listApps/index.ts +46 -25
  79. package/src/plugins/listAuthentications/index.test.ts +52 -15
  80. package/src/plugins/listAuthentications/index.ts +7 -2
  81. package/src/plugins/listAuthentications/schemas.ts +4 -0
  82. package/src/plugins/manifest/index.test.ts +503 -197
  83. package/src/plugins/manifest/index.ts +338 -82
  84. package/src/plugins/manifest/schemas.ts +9 -2
  85. package/src/sdk.ts +1 -5
  86. package/src/types/plugin.ts +3 -0
  87. package/src/types/sdk.ts +26 -21
  88. package/src/utils/domain-utils.test.ts +196 -2
  89. package/src/utils/domain-utils.ts +68 -35
  90. package/src/utils/file-utils.test.ts +73 -0
  91. package/src/utils/file-utils.ts +94 -0
  92. package/tsconfig.tsbuildinfo +1 -1
  93. package/dist/plugins/lockVersion/index.d.ts +0 -24
  94. package/dist/plugins/lockVersion/index.d.ts.map +0 -1
  95. package/dist/plugins/lockVersion/index.js +0 -72
  96. package/dist/plugins/lockVersion/index.test.d.ts +0 -2
  97. package/dist/plugins/lockVersion/index.test.d.ts.map +0 -1
  98. package/dist/plugins/lockVersion/index.test.js +0 -129
  99. package/dist/plugins/lockVersion/schemas.d.ts +0 -10
  100. package/dist/plugins/lockVersion/schemas.d.ts.map +0 -1
  101. package/dist/plugins/lockVersion/schemas.js +0 -6
  102. package/src/plugins/lockVersion/index.test.ts +0 -176
  103. package/src/plugins/lockVersion/index.ts +0 -112
  104. package/src/plugins/lockVersion/schemas.ts +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
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/client.ts CHANGED
@@ -437,19 +437,20 @@ class ZapierApiClient implements ApiClient {
437
437
  path: string,
438
438
  options: PollOptions = {},
439
439
  ): Promise<TOutput> => {
440
- return pollUntilComplete({
440
+ return pollUntilComplete<TOutput>({
441
441
  fetchPoll: () =>
442
442
  this.plainFetch(path, {
443
443
  method: "GET",
444
444
  searchParams: options.searchParams,
445
445
  authRequired: options.authRequired,
446
446
  }),
447
- maxAttempts: options.maxAttempts,
448
447
  initialDelay: options.initialDelay,
449
- maxDelay: options.maxDelay,
448
+ timeoutMs: options.timeoutMs,
450
449
  successStatus: options.successStatus,
451
450
  pendingStatus: options.pendingStatus,
452
- resultExtractor: options.resultExtractor,
451
+ resultExtractor: options.resultExtractor as
452
+ | ((response: unknown) => TOutput)
453
+ | undefined,
453
454
  });
454
455
  };
455
456
  }
@@ -0,0 +1,405 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { pollUntilComplete } from "./polling";
3
+ import {
4
+ ZapierTimeoutError,
5
+ ZapierApiError,
6
+ ZapierValidationError,
7
+ } from "../types/errors";
8
+
9
+ // Mock the timers/promises module
10
+ vi.mock("timers/promises", () => ({
11
+ setTimeout: vi.fn(() => Promise.resolve()),
12
+ }));
13
+
14
+ describe("pollUntilComplete", () => {
15
+ beforeEach(() => {
16
+ vi.clearAllMocks();
17
+ });
18
+
19
+ afterEach(() => {
20
+ vi.clearAllMocks();
21
+ });
22
+
23
+ describe("successful polling", () => {
24
+ it("should return immediately on first successful response", async () => {
25
+ const mockResponse = {
26
+ ok: true,
27
+ status: 200,
28
+ json: vi.fn().mockResolvedValue({ data: "success" }),
29
+ } as unknown as Response;
30
+
31
+ const fetchPoll = vi.fn().mockResolvedValue(mockResponse);
32
+
33
+ const result = await pollUntilComplete({ fetchPoll });
34
+ expect(result).toEqual({ data: "success" });
35
+ expect(fetchPoll).toHaveBeenCalledTimes(1);
36
+ });
37
+
38
+ it("should poll until success status is received", async () => {
39
+ const pendingResponse = {
40
+ ok: true,
41
+ status: 202,
42
+ } as Response;
43
+
44
+ const successResponse = {
45
+ ok: true,
46
+ status: 200,
47
+ json: vi.fn().mockResolvedValue({ data: "complete" }),
48
+ } as unknown as Response;
49
+
50
+ const fetchPoll = vi
51
+ .fn()
52
+ .mockResolvedValueOnce(pendingResponse)
53
+ .mockResolvedValueOnce(pendingResponse)
54
+ .mockResolvedValueOnce(successResponse);
55
+
56
+ const result = await pollUntilComplete({ fetchPoll });
57
+ expect(result).toEqual({ data: "complete" });
58
+ expect(fetchPoll).toHaveBeenCalledTimes(3);
59
+ });
60
+
61
+ it("should use custom success and pending status codes", async () => {
62
+ const pendingResponse = {
63
+ ok: true,
64
+ status: 301,
65
+ } as Response;
66
+
67
+ const successResponse = {
68
+ ok: true,
69
+ status: 204,
70
+ json: vi.fn().mockResolvedValue({ done: true }),
71
+ } as unknown as Response;
72
+
73
+ const fetchPoll = vi
74
+ .fn()
75
+ .mockResolvedValueOnce(pendingResponse)
76
+ .mockResolvedValueOnce(successResponse);
77
+
78
+ const result = await pollUntilComplete({
79
+ fetchPoll,
80
+ successStatus: 204,
81
+ pendingStatus: 301,
82
+ });
83
+ expect(result).toEqual({ done: true });
84
+ expect(fetchPoll).toHaveBeenCalledTimes(2);
85
+ });
86
+
87
+ it("should apply result extractor function", async () => {
88
+ const mockResponse = {
89
+ ok: true,
90
+ status: 200,
91
+ json: vi.fn().mockResolvedValue({
92
+ nested: { data: { value: "extracted" } },
93
+ }),
94
+ } as unknown as Response;
95
+
96
+ const fetchPoll = vi.fn().mockResolvedValue(mockResponse);
97
+ const resultExtractor = (response: any) => response.nested.data.value;
98
+
99
+ const result = await pollUntilComplete({
100
+ fetchPoll,
101
+ resultExtractor,
102
+ });
103
+ expect(result).toBe("extracted");
104
+ });
105
+ });
106
+
107
+ describe("initial delay", () => {
108
+ it("should wait for initial delay before first poll", async () => {
109
+ const mockResponse = {
110
+ ok: true,
111
+ status: 200,
112
+ json: vi.fn().mockResolvedValue({ data: "success" }),
113
+ } as unknown as Response;
114
+
115
+ const fetchPoll = vi.fn().mockResolvedValue(mockResponse);
116
+ const { setTimeout: mockSetTimeout } = await import("timers/promises");
117
+
118
+ const result = await pollUntilComplete({
119
+ fetchPoll,
120
+ initialDelay: 500,
121
+ });
122
+
123
+ expect(result).toEqual({ data: "success" });
124
+ expect(fetchPoll).toHaveBeenCalledTimes(1);
125
+ // Verify setTimeout was called with the initial delay
126
+ expect(mockSetTimeout).toHaveBeenCalledWith(500);
127
+ });
128
+ });
129
+
130
+ describe("error handling", () => {
131
+ it("should retry on transient network errors", async () => {
132
+ const successResponse = {
133
+ ok: true,
134
+ status: 200,
135
+ json: vi.fn().mockResolvedValue({ data: "recovered" }),
136
+ } as unknown as Response;
137
+
138
+ const fetchPoll = vi
139
+ .fn()
140
+ .mockRejectedValueOnce(new Error("Network error"))
141
+ .mockRejectedValueOnce(new Error("Network error"))
142
+ .mockResolvedValueOnce(successResponse);
143
+
144
+ const result = await pollUntilComplete({ fetchPoll });
145
+ expect(result).toEqual({ data: "recovered" });
146
+ expect(fetchPoll).toHaveBeenCalledTimes(3);
147
+ });
148
+
149
+ it("should fail after MAX_CONSECUTIVE_ERRORS network failures", async () => {
150
+ const fetchPoll = vi
151
+ .fn()
152
+ .mockRejectedValue(new Error("Persistent network error"));
153
+
154
+ await expect(pollUntilComplete({ fetchPoll })).rejects.toThrow(
155
+ ZapierApiError,
156
+ );
157
+ expect(fetchPoll).toHaveBeenCalledTimes(3);
158
+ });
159
+
160
+ it("should retry on HTTP error responses", async () => {
161
+ const errorResponse = {
162
+ ok: false,
163
+ status: 500,
164
+ statusText: "Internal Server Error",
165
+ } as Response;
166
+
167
+ const successResponse = {
168
+ ok: true,
169
+ status: 200,
170
+ json: vi.fn().mockResolvedValue({ data: "recovered" }),
171
+ } as unknown as Response;
172
+
173
+ const fetchPoll = vi
174
+ .fn()
175
+ .mockResolvedValueOnce(errorResponse)
176
+ .mockResolvedValueOnce(errorResponse)
177
+ .mockResolvedValueOnce(successResponse);
178
+
179
+ const result = await pollUntilComplete({ fetchPoll });
180
+ expect(result).toEqual({ data: "recovered" });
181
+ expect(fetchPoll).toHaveBeenCalledTimes(3);
182
+ });
183
+
184
+ it("should fail after MAX_CONSECUTIVE_ERRORS HTTP failures", async () => {
185
+ const errorResponse = {
186
+ ok: false,
187
+ status: 503,
188
+ statusText: "Service Unavailable",
189
+ } as Response;
190
+
191
+ const fetchPoll = vi.fn().mockResolvedValue(errorResponse);
192
+
193
+ await expect(pollUntilComplete({ fetchPoll })).rejects.toThrow(
194
+ ZapierApiError,
195
+ );
196
+ expect(fetchPoll).toHaveBeenCalledTimes(3);
197
+ });
198
+
199
+ it("should throw error for unexpected response status", async () => {
200
+ const unexpectedResponse = {
201
+ ok: true,
202
+ status: 301, // Not success (200) or pending (202)
203
+ } as Response;
204
+
205
+ const fetchPoll = vi.fn().mockResolvedValue(unexpectedResponse);
206
+
207
+ await expect(pollUntilComplete({ fetchPoll })).rejects.toThrow(
208
+ "Unexpected response status",
209
+ );
210
+ // Will retry 3 times before failing
211
+ expect(fetchPoll).toHaveBeenCalledTimes(3);
212
+ });
213
+
214
+ it("should throw error if successful response is not valid JSON", async () => {
215
+ const mockResponse = {
216
+ ok: true,
217
+ status: 200,
218
+ json: vi.fn().mockRejectedValue(new Error("Invalid JSON")),
219
+ } as unknown as Response;
220
+
221
+ const fetchPoll = vi.fn().mockResolvedValue(mockResponse);
222
+
223
+ await expect(pollUntilComplete({ fetchPoll })).rejects.toThrow(
224
+ "Failed to poll after 3 consecutive errors",
225
+ );
226
+ // Will retry 3 times before failing
227
+ expect(fetchPoll).toHaveBeenCalledTimes(3);
228
+ });
229
+ });
230
+
231
+ describe("timeout behavior", () => {
232
+ it("should timeout after specified duration", async () => {
233
+ // Mock Date.now to simulate time passing
234
+ const originalDateNow = Date.now;
235
+ let currentTime = 1000;
236
+ Date.now = vi.fn(() => currentTime);
237
+
238
+ const pendingResponse = {
239
+ ok: true,
240
+ status: 202,
241
+ } as Response;
242
+
243
+ const fetchPoll = vi.fn().mockImplementation(() => {
244
+ // Simulate time passing with each poll
245
+ currentTime += 500;
246
+ return Promise.resolve(pendingResponse);
247
+ });
248
+
249
+ try {
250
+ await expect(
251
+ pollUntilComplete({
252
+ fetchPoll,
253
+ timeoutMs: 1000,
254
+ initialDelay: 0,
255
+ }),
256
+ ).rejects.toThrow(ZapierTimeoutError);
257
+ } finally {
258
+ Date.now = originalDateNow;
259
+ }
260
+ });
261
+
262
+ it("should use default timeout when not specified", async () => {
263
+ // Mock Date.now to simulate time passing beyond default timeout
264
+ const originalDateNow = Date.now;
265
+ let currentTime = 1000;
266
+ Date.now = vi.fn(() => currentTime);
267
+
268
+ const pendingResponse = {
269
+ ok: true,
270
+ status: 202,
271
+ } as Response;
272
+
273
+ const fetchPoll = vi.fn().mockImplementation(() => {
274
+ // Simulate time passing beyond default timeout (180 seconds + buffer)
275
+ currentTime += 50000;
276
+ return Promise.resolve(pendingResponse);
277
+ });
278
+
279
+ try {
280
+ await expect(
281
+ pollUntilComplete({
282
+ fetchPoll,
283
+ initialDelay: 0,
284
+ }),
285
+ ).rejects.toThrow(ZapierTimeoutError);
286
+ } finally {
287
+ Date.now = originalDateNow;
288
+ }
289
+ });
290
+ });
291
+
292
+ describe("input validation", () => {
293
+ it("should throw error for invalid timeout", async () => {
294
+ const fetchPoll = vi.fn();
295
+
296
+ await expect(
297
+ pollUntilComplete({
298
+ fetchPoll,
299
+ timeoutMs: 0,
300
+ }),
301
+ ).rejects.toThrow(ZapierValidationError);
302
+
303
+ await expect(
304
+ pollUntilComplete({
305
+ fetchPoll,
306
+ timeoutMs: -1000,
307
+ }),
308
+ ).rejects.toThrow(/Timeout must be greater than 0/);
309
+ });
310
+
311
+ it("should throw error for negative initial delay", async () => {
312
+ const fetchPoll = vi.fn();
313
+
314
+ await expect(
315
+ pollUntilComplete({
316
+ fetchPoll,
317
+ initialDelay: -100,
318
+ }),
319
+ ).rejects.toThrow(ZapierValidationError);
320
+
321
+ await expect(
322
+ pollUntilComplete({
323
+ fetchPoll,
324
+ initialDelay: -1,
325
+ }),
326
+ ).rejects.toThrow(/Initial delay must be non-negative/);
327
+ });
328
+ });
329
+
330
+ describe("edge cases", () => {
331
+ it("should handle rapid status changes", async () => {
332
+ const responses = [
333
+ { ok: true, status: 202 } as Response,
334
+ { ok: false, status: 500 } as Response,
335
+ { ok: true, status: 202 } as Response,
336
+ {
337
+ ok: true,
338
+ status: 200,
339
+ json: vi.fn().mockResolvedValue({ data: "success" }),
340
+ } as unknown as Response,
341
+ ];
342
+
343
+ let index = 0;
344
+ const fetchPoll = vi.fn().mockImplementation(() => {
345
+ const response = responses[index];
346
+ index++;
347
+ return Promise.resolve(response);
348
+ });
349
+
350
+ const result = await pollUntilComplete({ fetchPoll });
351
+ expect(result).toEqual({ data: "success" });
352
+ expect(fetchPoll).toHaveBeenCalledTimes(4);
353
+ });
354
+
355
+ it("should handle immediate success with no initial delay", async () => {
356
+ const mockResponse = {
357
+ ok: true,
358
+ status: 200,
359
+ json: vi.fn().mockResolvedValue({ instant: true }),
360
+ } as unknown as Response;
361
+
362
+ const fetchPoll = vi.fn().mockResolvedValue(mockResponse);
363
+
364
+ const result = await pollUntilComplete({
365
+ fetchPoll,
366
+ initialDelay: 0,
367
+ });
368
+
369
+ expect(result).toEqual({ instant: true });
370
+ expect(fetchPoll).toHaveBeenCalledTimes(1);
371
+ });
372
+
373
+ it("should handle very short timeout", async () => {
374
+ // Mock Date.now to simulate time passing
375
+ const originalDateNow = Date.now;
376
+ let currentTime = 1000;
377
+ Date.now = vi.fn(() => currentTime);
378
+
379
+ const pendingResponse = {
380
+ ok: true,
381
+ status: 202,
382
+ } as Response;
383
+
384
+ const fetchPoll = vi.fn().mockImplementation(() => {
385
+ // Simulate time passing beyond the short timeout
386
+ currentTime += 200;
387
+ return Promise.resolve(pendingResponse);
388
+ });
389
+
390
+ try {
391
+ await expect(
392
+ pollUntilComplete({
393
+ fetchPoll,
394
+ timeoutMs: 100,
395
+ initialDelay: 0,
396
+ }),
397
+ ).rejects.toThrow(ZapierTimeoutError);
398
+ // Should have made at least 1 attempt before timing out
399
+ expect(fetchPoll.mock.calls.length).toBeGreaterThanOrEqual(1);
400
+ } finally {
401
+ Date.now = originalDateNow;
402
+ }
403
+ });
404
+ });
405
+ });