@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.
- package/CHANGELOG.md +12 -0
- package/README.md +10 -33
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +1 -2
- package/dist/api/polling.d.ts +36 -6
- package/dist/api/polling.d.ts.map +1 -1
- package/dist/api/polling.js +132 -28
- package/dist/api/polling.test.d.ts +2 -0
- package/dist/api/polling.test.d.ts.map +1 -0
- package/dist/api/polling.test.js +318 -0
- package/dist/api/types.d.ts +1 -2
- package/dist/api/types.d.ts.map +1 -1
- package/dist/index.cjs +489 -252
- package/dist/index.d.mts +182 -187
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.mjs +486 -251
- package/dist/plugins/apps/index.d.ts +4 -0
- package/dist/plugins/apps/index.d.ts.map +1 -1
- package/dist/plugins/getApp/index.d.ts +2 -7
- package/dist/plugins/getApp/index.d.ts.map +1 -1
- package/dist/plugins/getApp/index.js +9 -9
- package/dist/plugins/getApp/index.test.js +1 -1
- package/dist/plugins/getAuthentication/index.test.js +1 -1
- package/dist/plugins/listActions/index.d.ts +2 -4
- package/dist/plugins/listActions/index.d.ts.map +1 -1
- package/dist/plugins/listActions/index.js +1 -1
- package/dist/plugins/listActions/index.test.js +4 -4
- package/dist/plugins/listApps/index.d.ts +4 -7
- package/dist/plugins/listApps/index.d.ts.map +1 -1
- package/dist/plugins/listApps/index.js +33 -17
- package/dist/plugins/listApps/index.test.js +22 -2
- package/dist/plugins/listAuthentications/index.d.ts +2 -4
- package/dist/plugins/listAuthentications/index.d.ts.map +1 -1
- package/dist/plugins/listAuthentications/index.js +4 -0
- package/dist/plugins/listAuthentications/index.test.js +39 -13
- package/dist/plugins/listAuthentications/schemas.d.ts +3 -0
- package/dist/plugins/listAuthentications/schemas.d.ts.map +1 -1
- package/dist/plugins/listAuthentications/schemas.js +4 -0
- package/dist/plugins/manifest/index.d.ts +25 -9
- package/dist/plugins/manifest/index.d.ts.map +1 -1
- package/dist/plugins/manifest/index.js +239 -67
- package/dist/plugins/manifest/index.test.js +426 -171
- package/dist/plugins/manifest/schemas.d.ts +5 -1
- package/dist/plugins/manifest/schemas.d.ts.map +1 -1
- package/dist/plugins/manifest/schemas.js +1 -0
- package/dist/sdk.d.ts +5 -11
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +1 -4
- package/dist/types/plugin.d.ts +1 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/sdk.d.ts +6 -3
- package/dist/types/sdk.d.ts.map +1 -1
- package/dist/utils/domain-utils.d.ts +16 -0
- package/dist/utils/domain-utils.d.ts.map +1 -1
- package/dist/utils/domain-utils.js +46 -27
- package/dist/utils/domain-utils.test.js +157 -3
- package/dist/utils/file-utils.d.ts +4 -0
- package/dist/utils/file-utils.d.ts.map +1 -0
- package/dist/utils/file-utils.js +74 -0
- package/dist/utils/file-utils.test.d.ts +2 -0
- package/dist/utils/file-utils.test.d.ts.map +1 -0
- package/dist/utils/file-utils.test.js +51 -0
- package/package.json +1 -1
- package/src/api/client.ts +5 -4
- package/src/api/polling.test.ts +405 -0
- package/src/api/polling.ts +224 -44
- package/src/api/types.ts +1 -2
- package/src/index.ts +1 -1
- package/src/plugins/apps/index.ts +9 -2
- package/src/plugins/getApp/index.test.ts +1 -1
- package/src/plugins/getApp/index.ts +12 -14
- package/src/plugins/getAuthentication/index.test.ts +1 -1
- package/src/plugins/listActions/index.test.ts +8 -7
- package/src/plugins/listActions/index.ts +3 -3
- package/src/plugins/listApps/index.test.ts +23 -2
- package/src/plugins/listApps/index.ts +46 -25
- package/src/plugins/listAuthentications/index.test.ts +52 -15
- package/src/plugins/listAuthentications/index.ts +7 -2
- package/src/plugins/listAuthentications/schemas.ts +4 -0
- package/src/plugins/manifest/index.test.ts +503 -197
- package/src/plugins/manifest/index.ts +338 -82
- package/src/plugins/manifest/schemas.ts +9 -2
- package/src/sdk.ts +1 -5
- package/src/types/plugin.ts +3 -0
- package/src/types/sdk.ts +26 -21
- package/src/utils/domain-utils.test.ts +196 -2
- package/src/utils/domain-utils.ts +68 -35
- package/src/utils/file-utils.test.ts +73 -0
- package/src/utils/file-utils.ts +94 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/plugins/lockVersion/index.d.ts +0 -24
- package/dist/plugins/lockVersion/index.d.ts.map +0 -1
- package/dist/plugins/lockVersion/index.js +0 -72
- package/dist/plugins/lockVersion/index.test.d.ts +0 -2
- package/dist/plugins/lockVersion/index.test.d.ts.map +0 -1
- package/dist/plugins/lockVersion/index.test.js +0 -129
- package/dist/plugins/lockVersion/schemas.d.ts +0 -10
- package/dist/plugins/lockVersion/schemas.d.ts.map +0 -1
- package/dist/plugins/lockVersion/schemas.js +0 -6
- package/src/plugins/lockVersion/index.test.ts +0 -176
- package/src/plugins/lockVersion/index.ts +0 -112
- package/src/plugins/lockVersion/schemas.ts +0 -9
package/src/api/polling.ts
CHANGED
|
@@ -5,69 +5,249 @@
|
|
|
5
5
|
* with configurable retry logic and exponential backoff.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
ZapierTimeoutError,
|
|
10
|
+
ZapierApiError,
|
|
11
|
+
ZapierValidationError,
|
|
12
|
+
} from "../types/errors";
|
|
13
|
+
import { setTimeout } from "timers/promises";
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
// Constants
|
|
16
|
+
const DEFAULT_TIMEOUT_MS = 180_000;
|
|
17
|
+
const DEFAULT_SUCCESS_STATUS = 200;
|
|
18
|
+
const DEFAULT_PENDING_STATUS = 202;
|
|
19
|
+
const DEFAULT_INITIAL_DELAY_MS = 50;
|
|
20
|
+
const DEFAULT_MAX_POLLING_INTERVAL_MS = 10_000;
|
|
21
|
+
const MAX_CONSECUTIVE_ERRORS = 3;
|
|
22
|
+
const MAX_TIMEOUT_BUFFER_MS = 10_000;
|
|
23
|
+
const BASE_ERROR_BACKOFF_MS = 1_000;
|
|
24
|
+
const JITTER_FACTOR = 0.5;
|
|
25
|
+
|
|
26
|
+
// Polling stages: [threshold_ms, interval_ms]
|
|
27
|
+
// Note: These are default stages, actual hard timeout is enforced separately below
|
|
28
|
+
const DEFAULT_POLLING_STAGES = [
|
|
29
|
+
[125, 125], // Up to 125ms: poll every 125ms
|
|
30
|
+
[375, 250], // Up to 375ms: poll every 250ms
|
|
31
|
+
[875, 500], // Up to 875ms: poll every 500ms
|
|
32
|
+
[10_000, 1_000], // Up to 10s: poll every 1s
|
|
33
|
+
[30_000, 2_500], // Up to 30s: poll every 2.5s
|
|
34
|
+
[60_000, 5_000], // Up to 60s: poll every 5s
|
|
35
|
+
] as const satisfies Array<[number, number]>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for the polling function
|
|
39
|
+
*/
|
|
40
|
+
export interface PollOptions<TResult = unknown> {
|
|
41
|
+
/** Function that performs the HTTP request */
|
|
11
42
|
fetchPoll: () => Promise<Response>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
43
|
+
/** Maximum time to wait for completion (in milliseconds) */
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
/** HTTP status code indicating successful completion */
|
|
15
46
|
successStatus?: number;
|
|
47
|
+
/** HTTP status code indicating the operation is still pending */
|
|
16
48
|
pendingStatus?: number;
|
|
17
|
-
|
|
18
|
-
|
|
49
|
+
/** Function to extract the result from the response */
|
|
50
|
+
resultExtractor?: (response: unknown) => TResult;
|
|
51
|
+
/** Initial delay before the first poll attempt (in milliseconds) */
|
|
52
|
+
initialDelay?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const enum PollStatus {
|
|
56
|
+
Success = "success",
|
|
57
|
+
Continue = "continue",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Result of a poll operation
|
|
62
|
+
*/
|
|
63
|
+
export type PollResult<TResult = unknown> = {
|
|
64
|
+
result?: TResult;
|
|
65
|
+
status: PollStatus;
|
|
66
|
+
errorCount: number;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Helper to calculate wait time with jitter and error backoff
|
|
70
|
+
const calculateWaitTime = (
|
|
71
|
+
baseInterval: number,
|
|
72
|
+
errorCount: number,
|
|
73
|
+
): number => {
|
|
74
|
+
// Jitter to avoid thundering herd
|
|
75
|
+
const jitter = Math.random() * JITTER_FACTOR * baseInterval;
|
|
76
|
+
// More backoff added if errors are seen
|
|
77
|
+
const errorBackoff = Math.min(
|
|
78
|
+
BASE_ERROR_BACKOFF_MS * (errorCount / 2),
|
|
79
|
+
baseInterval * 2, // Cap error backoff at 2x the base interval
|
|
80
|
+
);
|
|
81
|
+
return Math.floor(baseInterval + jitter + errorBackoff);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const processResponse = async <TResult = unknown>(
|
|
85
|
+
response: Response,
|
|
86
|
+
successStatus: number,
|
|
87
|
+
pendingStatus: number,
|
|
88
|
+
resultExtractor: (response: unknown) => TResult,
|
|
89
|
+
errorCount: number,
|
|
90
|
+
): Promise<PollResult<TResult>> => {
|
|
91
|
+
// Handle other error responses
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
return {
|
|
94
|
+
status: PollStatus.Continue,
|
|
95
|
+
// If for some reason the status is pending, we don't want to increment the error count
|
|
96
|
+
errorCount:
|
|
97
|
+
response.status === pendingStatus ? errorCount : errorCount + 1,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for successful completion
|
|
102
|
+
if (response.status === successStatus) {
|
|
103
|
+
try {
|
|
104
|
+
const resultJson = await response.json();
|
|
105
|
+
return {
|
|
106
|
+
result: resultExtractor(resultJson),
|
|
107
|
+
status: PollStatus.Success,
|
|
108
|
+
errorCount: 0,
|
|
109
|
+
};
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new ZapierApiError(
|
|
112
|
+
"Result extractor failed to parse successful response as JSON",
|
|
113
|
+
{
|
|
114
|
+
statusCode: response.status,
|
|
115
|
+
cause: error,
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// If it's not pending, it's unexpected
|
|
122
|
+
if (response.status !== pendingStatus) {
|
|
123
|
+
throw new ZapierApiError(
|
|
124
|
+
`Unexpected response status during polling: ${response.status}`,
|
|
125
|
+
{
|
|
126
|
+
statusCode: response.status,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// It's still pending, so we continue polling
|
|
132
|
+
return {
|
|
133
|
+
status: PollStatus.Continue,
|
|
134
|
+
errorCount: 0,
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Polls an endpoint until completion, timeout, or error
|
|
140
|
+
* @param options Configuration options for polling
|
|
141
|
+
* @returns The extracted result from the successful response
|
|
142
|
+
* @throws {ZapierValidationError} When the input parameters are invalid
|
|
143
|
+
* @throws {ZapierTimeoutError} When the operation times out
|
|
144
|
+
* @throws {ZapierApiError} When the API returns consecutive errors
|
|
145
|
+
*/
|
|
146
|
+
export async function pollUntilComplete<TResult = unknown>(
|
|
147
|
+
options: PollOptions<TResult>,
|
|
148
|
+
): Promise<TResult> {
|
|
19
149
|
const {
|
|
20
150
|
fetchPoll,
|
|
21
|
-
|
|
22
|
-
initialDelay =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
resultExtractor = (response) => response,
|
|
151
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
152
|
+
initialDelay = DEFAULT_INITIAL_DELAY_MS,
|
|
153
|
+
successStatus = DEFAULT_SUCCESS_STATUS,
|
|
154
|
+
pendingStatus = DEFAULT_PENDING_STATUS,
|
|
155
|
+
resultExtractor = (response) => response as TResult,
|
|
27
156
|
} = options;
|
|
28
157
|
|
|
29
|
-
|
|
158
|
+
// Validate input parameters
|
|
159
|
+
if (timeoutMs <= 0) {
|
|
160
|
+
throw new ZapierValidationError("Timeout must be greater than 0", {
|
|
161
|
+
details: { timeoutMs },
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (initialDelay < 0) {
|
|
166
|
+
throw new ZapierValidationError("Initial delay must be non-negative", {
|
|
167
|
+
details: { initialDelay },
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const startTime = Date.now();
|
|
172
|
+
let attempts = 0;
|
|
30
173
|
let errorCount = 0;
|
|
31
174
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
175
|
+
// Build polling stages with the actual timeout appended
|
|
176
|
+
const pollingStages = [
|
|
177
|
+
...DEFAULT_POLLING_STAGES,
|
|
178
|
+
[timeoutMs + MAX_TIMEOUT_BUFFER_MS, DEFAULT_MAX_POLLING_INTERVAL_MS], // Up to timeout + 10s: poll every 10s
|
|
179
|
+
] as const satisfies Array<[number, number]>;
|
|
180
|
+
|
|
181
|
+
// Apply initial delay if specified
|
|
182
|
+
if (initialDelay > 0) {
|
|
183
|
+
await setTimeout(initialDelay);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
while (true) {
|
|
187
|
+
attempts++;
|
|
188
|
+
const elapsedTime = Date.now() - startTime;
|
|
189
|
+
|
|
190
|
+
// Find the current polling stage
|
|
191
|
+
const pollingInterval = pollingStages.find(
|
|
192
|
+
([maxTimeForStage, _interval]) => {
|
|
193
|
+
return elapsedTime < maxTimeForStage;
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// If there isn't a current stage, throw timeout error
|
|
198
|
+
if (!pollingInterval) {
|
|
199
|
+
throw new ZapierTimeoutError(
|
|
200
|
+
`Operation timed out after ${Math.floor(elapsedTime / 1000)}s (${attempts} attempts)`,
|
|
201
|
+
{
|
|
202
|
+
attempts,
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Wait before polling (except on first attempt)
|
|
208
|
+
if (attempts > 1) {
|
|
209
|
+
const waitTime = calculateWaitTime(pollingInterval[1], errorCount);
|
|
210
|
+
await setTimeout(waitTime);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Perform the poll request
|
|
214
|
+
try {
|
|
215
|
+
const response = await fetchPoll();
|
|
216
|
+
const {
|
|
217
|
+
result,
|
|
218
|
+
errorCount: newErrorCount,
|
|
219
|
+
status,
|
|
220
|
+
} = await processResponse<TResult>(
|
|
221
|
+
response,
|
|
222
|
+
successStatus,
|
|
223
|
+
pendingStatus,
|
|
224
|
+
resultExtractor,
|
|
225
|
+
errorCount,
|
|
226
|
+
);
|
|
227
|
+
errorCount = newErrorCount;
|
|
228
|
+
|
|
229
|
+
if (status === PollStatus.Success) {
|
|
230
|
+
return result as TResult;
|
|
47
231
|
}
|
|
48
|
-
} else {
|
|
49
|
-
// Error occurred - increment error count
|
|
50
|
-
errorCount++;
|
|
51
232
|
|
|
52
|
-
if (errorCount >=
|
|
233
|
+
if (errorCount >= MAX_CONSECUTIVE_ERRORS) {
|
|
53
234
|
// Too many consecutive errors, fail
|
|
54
235
|
throw new ZapierApiError(
|
|
55
236
|
`Poll request failed: ${response.status} ${response.statusText}`,
|
|
56
237
|
{ statusCode: response.status },
|
|
57
238
|
);
|
|
58
239
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
240
|
+
} catch (error) {
|
|
241
|
+
errorCount++;
|
|
242
|
+
if (errorCount >= MAX_CONSECUTIVE_ERRORS) {
|
|
243
|
+
throw new ZapierApiError(
|
|
244
|
+
`Failed to poll after ${errorCount} consecutive errors: ${error instanceof Error ? error.message : String(error)}`,
|
|
245
|
+
{
|
|
246
|
+
cause: error,
|
|
247
|
+
},
|
|
248
|
+
);
|
|
65
249
|
}
|
|
66
250
|
}
|
|
251
|
+
// Continue polling if status is pending
|
|
67
252
|
}
|
|
68
|
-
|
|
69
|
-
throw new ZapierTimeoutError(
|
|
70
|
-
`Operation timed out after ${maxAttempts} attempts`,
|
|
71
|
-
{ attempts: maxAttempts, maxAttempts: maxAttempts },
|
|
72
|
-
);
|
|
73
253
|
}
|
package/src/api/types.ts
CHANGED
|
@@ -88,9 +88,8 @@ export interface RequestOptions {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
export interface PollOptions extends RequestOptions {
|
|
91
|
-
maxAttempts?: number;
|
|
92
91
|
initialDelay?: number;
|
|
93
|
-
|
|
92
|
+
timeoutMs?: number;
|
|
94
93
|
successStatus?: number;
|
|
95
94
|
pendingStatus?: number;
|
|
96
95
|
resultExtractor?: (response: unknown) => unknown;
|
package/src/index.ts
CHANGED
|
@@ -17,7 +17,6 @@ export * from "./plugins/findUniqueAuthentication";
|
|
|
17
17
|
export * from "./plugins/runAction";
|
|
18
18
|
export * from "./plugins/request";
|
|
19
19
|
export * from "./plugins/manifest";
|
|
20
|
-
export * from "./plugins/lockVersion";
|
|
21
20
|
export * from "./plugins/getProfile";
|
|
22
21
|
export * from "./plugins/api";
|
|
23
22
|
|
|
@@ -74,6 +73,7 @@ export type {
|
|
|
74
73
|
PluginDependencies,
|
|
75
74
|
PluginOptions,
|
|
76
75
|
GetSdkType,
|
|
76
|
+
GetContextType,
|
|
77
77
|
Sdk,
|
|
78
78
|
} from "./types/plugin";
|
|
79
79
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { ActionExecutionOptions } from "./types";
|
|
2
|
-
import type { ActionProxy } from "./types";
|
|
1
|
+
import type { ActionExecutionOptions, ActionProxy } from "./types";
|
|
3
2
|
import { ZapierValidationError } from "../../types/errors";
|
|
4
3
|
import type { Plugin, GetSdkType } from "../../types/plugin";
|
|
5
4
|
import type { FetchPluginProvides } from "../fetch/index";
|
|
@@ -173,3 +172,11 @@ export const appsPlugin: Plugin<
|
|
|
173
172
|
apps: createAppsProxy({ sdk }),
|
|
174
173
|
};
|
|
175
174
|
};
|
|
175
|
+
|
|
176
|
+
// Export types for use in generated code
|
|
177
|
+
export type { ActionExecutionOptions } from "./types";
|
|
178
|
+
export type { ActionExecutionResult } from "../../api/types";
|
|
179
|
+
|
|
180
|
+
// Interface for generated apps - will be augmented by generated .d.ts files
|
|
181
|
+
// This interface will contain only the specifically typed apps
|
|
182
|
+
export interface ZapierSdkApps {}
|
|
@@ -12,8 +12,8 @@ import { manifestPlugin } from "../manifest";
|
|
|
12
12
|
function createTestSdk() {
|
|
13
13
|
return createSdk()
|
|
14
14
|
.addPlugin(apiPlugin, { fetch: global.fetch })
|
|
15
|
-
.addPlugin(listAppsPlugin)
|
|
16
15
|
.addPlugin(manifestPlugin)
|
|
16
|
+
.addPlugin(listAppsPlugin)
|
|
17
17
|
.addPlugin(getAppPlugin);
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -5,8 +5,7 @@ import type { GetAppOptions } from "./schemas";
|
|
|
5
5
|
import type { AppItem } from "../../types/domain";
|
|
6
6
|
import { ZapierAppNotFoundError } from "../../types/errors";
|
|
7
7
|
import type { GetSdkType } from "../../types/plugin";
|
|
8
|
-
import type {
|
|
9
|
-
import type { ManifestPluginProvides } from "../manifest";
|
|
8
|
+
import type { ListAppsPluginProvides } from "../listApps";
|
|
10
9
|
|
|
11
10
|
// GetApp plugin provides interface - getApp goes directly to SDK root
|
|
12
11
|
export interface GetAppPluginProvides {
|
|
@@ -22,21 +21,20 @@ export interface GetAppPluginProvides {
|
|
|
22
21
|
|
|
23
22
|
// GetApp plugin depends on listApps SDK function
|
|
24
23
|
export const getAppPlugin: Plugin<
|
|
25
|
-
GetSdkType<
|
|
26
|
-
{
|
|
24
|
+
GetSdkType<ListAppsPluginProvides>,
|
|
25
|
+
{},
|
|
27
26
|
GetAppPluginProvides
|
|
28
|
-
> = ({
|
|
27
|
+
> = ({ sdk }) => {
|
|
29
28
|
const getApp = createFunction(async function getApp(options: GetAppOptions) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
29
|
+
const appsIterator = sdk.listApps({ appKeys: [options.appKey] }).items();
|
|
30
|
+
for await (const app of appsIterator) {
|
|
31
|
+
return {
|
|
32
|
+
data: app,
|
|
33
|
+
};
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
35
|
+
throw new ZapierAppNotFoundError("App not found", {
|
|
36
|
+
appKey: options.appKey,
|
|
37
|
+
});
|
|
40
38
|
}, GetAppSchema);
|
|
41
39
|
|
|
42
40
|
// Return flat structure - getApp goes directly to SDK
|
|
@@ -45,8 +45,8 @@ describe("getAuthentication plugin", () => {
|
|
|
45
45
|
function createTestSdk() {
|
|
46
46
|
return createSdk()
|
|
47
47
|
.addPlugin(apiPlugin)
|
|
48
|
-
.addPlugin(listAppsPlugin)
|
|
49
48
|
.addPlugin(manifestPlugin)
|
|
49
|
+
.addPlugin(listAppsPlugin)
|
|
50
50
|
.addPlugin(getAuthenticationPlugin);
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -68,6 +68,8 @@ describe("listActions plugin", () => {
|
|
|
68
68
|
.mockResolvedValue("SlackCLIAPI@1.21.1");
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
const mockResolveAppKeys = vi.fn().mockResolvedValue([]);
|
|
72
|
+
|
|
71
73
|
function createTestSdk() {
|
|
72
74
|
// Create a proper plugin chain with context dependencies
|
|
73
75
|
|
|
@@ -76,18 +78,17 @@ describe("listActions plugin", () => {
|
|
|
76
78
|
.addPlugin(() => ({
|
|
77
79
|
context: {
|
|
78
80
|
api: mockApiClient,
|
|
79
|
-
getVersionedImplementationId: mockGetVersionedImplementationId,
|
|
80
81
|
},
|
|
81
82
|
}))
|
|
82
|
-
.addPlugin(listAppsPlugin)
|
|
83
83
|
.addPlugin(() => ({
|
|
84
84
|
context: {
|
|
85
85
|
manifest: null,
|
|
86
|
-
getManifestEntry: () => null,
|
|
87
86
|
getVersionedImplementationId: mockGetVersionedImplementationId,
|
|
88
|
-
|
|
87
|
+
resolveAppKeys: mockResolveAppKeys,
|
|
88
|
+
updateManifestEntry: vi.fn().mockResolvedValue(["test-key", {}]),
|
|
89
89
|
},
|
|
90
90
|
}))
|
|
91
|
+
.addPlugin(listAppsPlugin)
|
|
91
92
|
.addPlugin(listActionsPlugin);
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -166,7 +167,7 @@ describe("listActions plugin", () => {
|
|
|
166
167
|
|
|
167
168
|
expect(result.data).toHaveLength(2); // Only write actions
|
|
168
169
|
expect(
|
|
169
|
-
result.data.every((action) => action.action_type === "write"),
|
|
170
|
+
result.data.every((action: any) => action.action_type === "write"),
|
|
170
171
|
).toBe(true);
|
|
171
172
|
});
|
|
172
173
|
});
|
|
@@ -309,9 +310,9 @@ describe("listActions plugin", () => {
|
|
|
309
310
|
|
|
310
311
|
expect(result.data).toHaveLength(2);
|
|
311
312
|
expect(
|
|
312
|
-
result.data.every((action) => action.action_type === "write"),
|
|
313
|
+
result.data.every((action: any) => action.action_type === "write"),
|
|
313
314
|
).toBe(true);
|
|
314
|
-
expect(result.data.map((action) => action.key)).toEqual([
|
|
315
|
+
expect(result.data.map((action: any) => action.key)).toEqual([
|
|
315
316
|
"send_message",
|
|
316
317
|
"create_channel",
|
|
317
318
|
]);
|
|
@@ -32,11 +32,11 @@ export interface ListActionsPluginProvides {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export const listActionsPlugin: Plugin<
|
|
35
|
-
GetSdkType<ManifestPluginProvides>,
|
|
35
|
+
GetSdkType<ManifestPluginProvides>,
|
|
36
36
|
{
|
|
37
37
|
api: ApiClient;
|
|
38
38
|
getVersionedImplementationId: GetVersionedImplementationId;
|
|
39
|
-
},
|
|
39
|
+
},
|
|
40
40
|
ListActionsPluginProvides
|
|
41
41
|
> = ({ context }) => {
|
|
42
42
|
const listActions = createPaginatedFunction(async function listActionsPage(
|
|
@@ -44,7 +44,7 @@ export const listActionsPlugin: Plugin<
|
|
|
44
44
|
): Promise<ListActionsPage> {
|
|
45
45
|
const { api, getVersionedImplementationId } = context;
|
|
46
46
|
|
|
47
|
-
// Use
|
|
47
|
+
// Use getVersionedImplementationId (optimized to check manifest first)
|
|
48
48
|
const selectedApi = await getVersionedImplementationId(options.appKey);
|
|
49
49
|
|
|
50
50
|
if (!selectedApi) {
|
|
@@ -28,9 +28,26 @@ const mockAppsResponse = {
|
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
const mockResolveAppKeys = vi
|
|
32
|
+
.fn()
|
|
33
|
+
.mockImplementation(async ({ appKeys }: { appKeys: string[] }) => {
|
|
34
|
+
// Mock implementation that returns resolved locators for the app keys
|
|
35
|
+
return appKeys.map((appKey) => ({
|
|
36
|
+
lookupAppKey: appKey,
|
|
37
|
+
implementationName: appKey, // For testing, use appKey as implementationName
|
|
38
|
+
slug: appKey.toLowerCase(),
|
|
39
|
+
version: undefined,
|
|
40
|
+
}));
|
|
41
|
+
});
|
|
42
|
+
|
|
31
43
|
function createTestSdk() {
|
|
32
44
|
return createSdk()
|
|
33
45
|
.addPlugin(apiPlugin, { fetch: global.fetch })
|
|
46
|
+
.addPlugin(() => ({
|
|
47
|
+
context: {
|
|
48
|
+
resolveAppKeys: mockResolveAppKeys,
|
|
49
|
+
},
|
|
50
|
+
}))
|
|
34
51
|
.addPlugin(listAppsPlugin as any);
|
|
35
52
|
}
|
|
36
53
|
|
|
@@ -100,7 +117,11 @@ describe("listApps plugin", () => {
|
|
|
100
117
|
expect((context.api as any).get).toHaveBeenCalledWith(
|
|
101
118
|
"/api/v4/implementations-meta/lookup/",
|
|
102
119
|
{
|
|
103
|
-
searchParams: {
|
|
120
|
+
searchParams: {
|
|
121
|
+
latest_only: "true",
|
|
122
|
+
limit: "100",
|
|
123
|
+
selected_apis: "",
|
|
124
|
+
},
|
|
104
125
|
},
|
|
105
126
|
);
|
|
106
127
|
});
|
|
@@ -338,7 +359,7 @@ describe("listApps plugin", () => {
|
|
|
338
359
|
expect.stringContaining("implementations-meta"),
|
|
339
360
|
expect.objectContaining({
|
|
340
361
|
searchParams: expect.objectContaining({
|
|
341
|
-
selected_apis: "SlackCLIAPI,GitHubCLIAPI",
|
|
362
|
+
selected_apis: "SlackCLIAPI@latest,GitHubCLIAPI@latest",
|
|
342
363
|
}),
|
|
343
364
|
}),
|
|
344
365
|
);
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import type { Plugin } from "../../types/plugin";
|
|
2
|
-
import type { ApiClient } from "../../api/types";
|
|
1
|
+
import type { GetContextType, Plugin } from "../../types/plugin";
|
|
3
2
|
import { createPaginatedFunction } from "../../utils/function-utils";
|
|
4
3
|
import { ListAppsSchema } from "./schemas";
|
|
5
4
|
import type { ListAppsOptions, ListAppsPage } from "./schemas";
|
|
6
5
|
import type { AppItem } from "../../types/domain";
|
|
6
|
+
import type { ResolvedAppLocator } from "../../utils/domain-utils";
|
|
7
7
|
import {
|
|
8
|
-
groupAppKeysByType,
|
|
9
8
|
normalizeImplementationMetaToAppItem,
|
|
10
9
|
splitVersionedKey,
|
|
10
|
+
toAppLocator,
|
|
11
|
+
toImplementationId,
|
|
11
12
|
} from "../../utils/domain-utils";
|
|
12
13
|
import { extractCursor } from "../../utils/function-utils";
|
|
13
14
|
import type { ImplementationsMetaResponse } from "../../api/types";
|
|
15
|
+
import type { ManifestPluginProvides } from "../manifest";
|
|
16
|
+
import type { ApiPluginProvides } from "../api";
|
|
14
17
|
// ListApps plugin provides interface - listApps goes directly to SDK root
|
|
15
18
|
export interface ListAppsPluginProvides {
|
|
16
19
|
listApps: (options?: ListAppsOptions) => Promise<{ data: AppItem[] }> &
|
|
@@ -26,10 +29,9 @@ export interface ListAppsPluginProvides {
|
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
// Direct plugin function - takes options + sdk + context in one object
|
|
30
32
|
export const listAppsPlugin: Plugin<
|
|
31
|
-
{},
|
|
32
|
-
|
|
33
|
+
{},
|
|
34
|
+
GetContextType<ApiPluginProvides & ManifestPluginProvides>,
|
|
33
35
|
ListAppsPluginProvides
|
|
34
36
|
> = ({ context }) => {
|
|
35
37
|
const listApps = createPaginatedFunction(async function listAppsPage(
|
|
@@ -38,9 +40,28 @@ export const listAppsPlugin: Plugin<
|
|
|
38
40
|
const api = context.api;
|
|
39
41
|
const opts = options;
|
|
40
42
|
|
|
41
|
-
const
|
|
42
|
-
(
|
|
43
|
-
);
|
|
43
|
+
const appLocators = await context.resolveAppKeys({
|
|
44
|
+
appKeys: [...(opts.appKeys ?? [])],
|
|
45
|
+
});
|
|
46
|
+
const implementationNameToLocator: Record<string, ResolvedAppLocator[]> =
|
|
47
|
+
{};
|
|
48
|
+
for (const locator of appLocators) {
|
|
49
|
+
implementationNameToLocator[locator.implementationName] = [
|
|
50
|
+
...(implementationNameToLocator[locator.implementationName] ?? []),
|
|
51
|
+
locator,
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
const duplicatedLookupAppKeys = Object.keys(implementationNameToLocator)
|
|
55
|
+
.filter((key) => implementationNameToLocator[key].length > 1)
|
|
56
|
+
.map((key) => implementationNameToLocator[key])
|
|
57
|
+
.flat()
|
|
58
|
+
.map((locator) => locator.lookupAppKey);
|
|
59
|
+
|
|
60
|
+
if (duplicatedLookupAppKeys.length > 0) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Duplicate lookup app keys found: ${duplicatedLookupAppKeys.join(", ")}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
44
65
|
|
|
45
66
|
if (opts.search) {
|
|
46
67
|
const searchParams: Record<string, string> = {};
|
|
@@ -58,12 +79,18 @@ export const listAppsPlugin: Plugin<
|
|
|
58
79
|
normalizeImplementationMetaToAppItem,
|
|
59
80
|
);
|
|
60
81
|
|
|
61
|
-
const
|
|
82
|
+
const implementationNameSet = new Set<string>(
|
|
83
|
+
appLocators.map((locator) => locator.implementationName),
|
|
84
|
+
);
|
|
62
85
|
|
|
63
86
|
for (const implementation of implementations) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
87
|
+
const [implementationName] = splitVersionedKey(implementation.key);
|
|
88
|
+
if (!implementationNameSet.has(implementationName)) {
|
|
89
|
+
implementationNameSet.add(implementationName);
|
|
90
|
+
appLocators.push({
|
|
91
|
+
...toAppLocator(implementation.key),
|
|
92
|
+
implementationName,
|
|
93
|
+
});
|
|
67
94
|
}
|
|
68
95
|
}
|
|
69
96
|
}
|
|
@@ -74,23 +101,17 @@ export const listAppsPlugin: Plugin<
|
|
|
74
101
|
searchParams.limit = opts.pageSize.toString();
|
|
75
102
|
}
|
|
76
103
|
|
|
77
|
-
|
|
104
|
+
if (appLocators.length === 0) {
|
|
105
|
+
searchParams.latest_only = "true";
|
|
106
|
+
}
|
|
78
107
|
|
|
79
108
|
if (opts.cursor) {
|
|
80
109
|
searchParams.offset = opts.cursor;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (groupedAppKeys.selectedApi.length > 0) {
|
|
87
|
-
searchParams.selected_apis = groupedAppKeys.selectedApi.join(",");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (groupedAppKeys.slug.length > 0) {
|
|
91
|
-
searchParams.slugs = groupedAppKeys.slug.join(",");
|
|
92
|
-
}
|
|
93
|
-
}
|
|
112
|
+
searchParams.selected_apis = appLocators
|
|
113
|
+
.map((locator) => toImplementationId(locator))
|
|
114
|
+
.join(",");
|
|
94
115
|
|
|
95
116
|
const implementationsEnvelope: ImplementationsMetaResponse = await api.get(
|
|
96
117
|
"/api/v4/implementations-meta/lookup/",
|