@zapier/zapier-sdk 0.0.2 → 0.1.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/dist/api/auth.d.ts +8 -0
- package/dist/api/auth.js +29 -0
- package/dist/api/client.d.ts +8 -0
- package/dist/api/client.js +102 -0
- package/dist/api/debug.d.ts +12 -0
- package/dist/api/debug.js +50 -0
- package/dist/api/index.d.ts +26 -0
- package/dist/api/index.js +51 -0
- package/dist/api/polling.d.ts +17 -0
- package/dist/api/polling.js +34 -0
- package/dist/{types.d.ts → api/types.d.ts} +37 -76
- package/dist/api/types.js +9 -0
- package/dist/functions/bundleCode/index.d.ts +11 -0
- package/dist/functions/bundleCode/index.js +91 -0
- package/dist/functions/bundleCode/info.d.ts +27 -0
- package/dist/functions/bundleCode/info.js +11 -0
- package/dist/functions/bundleCode/schemas.d.ts +27 -0
- package/dist/functions/bundleCode/schemas.js +22 -0
- package/dist/functions/generateTypes/index.d.ts +11 -0
- package/dist/functions/generateTypes/index.js +305 -0
- package/dist/functions/generateTypes/info.d.ts +21 -0
- package/dist/functions/generateTypes/info.js +11 -0
- package/dist/functions/generateTypes/schemas.d.ts +30 -0
- package/dist/functions/generateTypes/schemas.js +14 -0
- package/dist/functions/getAction/index.d.ts +12 -0
- package/dist/functions/getAction/index.js +26 -0
- package/dist/functions/getAction/info.d.ts +18 -0
- package/dist/functions/getAction/info.js +11 -0
- package/dist/functions/getAction/schemas.d.ts +30 -0
- package/dist/functions/getAction/schemas.js +13 -0
- package/dist/functions/getApp/index.d.ts +12 -0
- package/dist/functions/getApp/index.js +37 -0
- package/dist/functions/getApp/info.d.ts +12 -0
- package/dist/functions/getApp/info.js +11 -0
- package/dist/functions/getApp/schemas.d.ts +24 -0
- package/dist/functions/getApp/schemas.js +11 -0
- package/dist/functions/listActions/index.d.ts +12 -0
- package/dist/functions/listActions/index.js +128 -0
- package/dist/functions/listActions/info.d.ts +15 -0
- package/dist/functions/listActions/info.js +11 -0
- package/dist/functions/listActions/schemas.d.ts +27 -0
- package/dist/functions/listActions/schemas.js +14 -0
- package/dist/functions/listApps/index.d.ts +12 -0
- package/dist/functions/listApps/index.js +50 -0
- package/dist/functions/listApps/info.d.ts +18 -0
- package/dist/functions/listApps/info.js +11 -0
- package/dist/functions/listApps/schemas.d.ts +30 -0
- package/dist/functions/listApps/schemas.js +15 -0
- package/dist/functions/listAuths/index.d.ts +12 -0
- package/dist/functions/listAuths/index.js +118 -0
- package/dist/functions/listAuths/info.d.ts +24 -0
- package/dist/functions/listAuths/info.js +11 -0
- package/dist/functions/listAuths/schemas.d.ts +36 -0
- package/dist/functions/listAuths/schemas.js +17 -0
- package/dist/functions/listFields/index.d.ts +12 -0
- package/dist/functions/listFields/index.js +65 -0
- package/dist/functions/listFields/info.d.ts +24 -0
- package/dist/functions/listFields/info.js +11 -0
- package/dist/functions/listFields/schemas.d.ts +36 -0
- package/dist/functions/listFields/schemas.js +17 -0
- package/dist/functions/runAction/index.d.ts +12 -0
- package/dist/functions/runAction/index.js +157 -0
- package/dist/functions/runAction/info.d.ts +24 -0
- package/dist/functions/runAction/info.js +11 -0
- package/dist/functions/runAction/schemas.d.ts +36 -0
- package/dist/functions/runAction/schemas.js +15 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.js +27 -4
- package/dist/plugins/apps/index.d.ts +8 -0
- package/dist/plugins/apps/index.js +77 -0
- package/dist/plugins/apps/info.d.ts +6 -0
- package/dist/plugins/apps/info.js +13 -0
- package/dist/plugins/apps/types.d.ts +21 -0
- package/dist/plugins/apps/types.js +2 -0
- package/dist/resolvers/actionKey.d.ts +8 -0
- package/dist/resolvers/actionKey.js +20 -0
- package/dist/resolvers/actionType.d.ts +8 -0
- package/dist/resolvers/actionType.js +21 -0
- package/dist/resolvers/appKey.d.ts +6 -0
- package/dist/resolvers/appKey.js +8 -0
- package/dist/resolvers/authenticationId.d.ts +8 -0
- package/dist/resolvers/authenticationId.js +29 -0
- package/dist/resolvers/index.d.ts +39 -0
- package/dist/resolvers/index.js +105 -0
- package/dist/resolvers/inputs.d.ts +7 -0
- package/dist/resolvers/inputs.js +15 -0
- package/dist/schema-utils.d.ts +39 -0
- package/dist/schema-utils.js +52 -0
- package/dist/schemas/Action.d.ts +21 -0
- package/dist/schemas/Action.js +31 -0
- package/dist/schemas/App.d.ts +19 -0
- package/dist/schemas/App.js +32 -0
- package/dist/schemas/Auth.d.ts +30 -0
- package/dist/schemas/Auth.js +49 -0
- package/dist/schemas/Field.d.ts +15 -0
- package/dist/schemas/Field.js +25 -0
- package/dist/sdk.d.ts +3 -4
- package/dist/sdk.js +96 -11
- package/dist/types/domain.d.ts +22 -0
- package/dist/types/domain.js +21 -0
- package/dist/types/properties.d.ts +21 -0
- package/dist/types/properties.js +45 -0
- package/dist/types/sdk.d.ts +21 -0
- package/dist/types/sdk.js +2 -0
- package/package.json +4 -2
- package/src/api/auth.ts +28 -0
- package/src/api/client.ts +148 -0
- package/src/api/debug.ts +58 -0
- package/src/api/index.ts +83 -0
- package/src/api/polling.ts +56 -0
- package/src/{types.ts → api/types.ts} +51 -118
- package/src/functions/bundleCode/index.ts +78 -0
- package/src/functions/bundleCode/info.ts +9 -0
- package/src/functions/bundleCode/schemas.ts +30 -0
- package/src/functions/generateTypes/index.ts +348 -0
- package/src/functions/generateTypes/info.ts +9 -0
- package/src/functions/generateTypes/schemas.ts +38 -0
- package/src/functions/getAction/index.ts +33 -0
- package/src/functions/getAction/info.ts +9 -0
- package/src/functions/getAction/schemas.ts +35 -0
- package/src/functions/getApp/index.ts +41 -0
- package/src/functions/getApp/info.ts +9 -0
- package/src/functions/getApp/schemas.ts +31 -0
- package/src/functions/listActions/index.ts +149 -0
- package/src/functions/listActions/info.ts +9 -0
- package/src/functions/listActions/schemas.ts +40 -0
- package/src/functions/listApps/index.ts +60 -0
- package/src/functions/listApps/info.ts +9 -0
- package/src/functions/listApps/schemas.ts +43 -0
- package/src/functions/listAuths/index.ts +153 -0
- package/src/functions/listAuths/info.ts +9 -0
- package/src/functions/listAuths/schemas.ts +48 -0
- package/src/functions/listFields/index.ts +86 -0
- package/src/functions/listFields/info.ts +9 -0
- package/src/functions/listFields/schemas.ts +46 -0
- package/src/functions/runAction/index.ts +258 -0
- package/src/functions/runAction/info.ts +9 -0
- package/src/functions/runAction/schemas.ts +41 -0
- package/src/index.ts +24 -4
- package/src/plugins/apps/index.ts +144 -0
- package/src/plugins/apps/info.ts +12 -0
- package/src/plugins/apps/types.ts +34 -0
- package/src/resolvers/actionKey.ts +33 -0
- package/src/resolvers/actionType.ts +30 -0
- package/src/resolvers/appKey.ts +11 -0
- package/src/resolvers/authenticationId.ts +38 -0
- package/src/resolvers/index.ts +117 -0
- package/src/resolvers/inputs.ts +23 -0
- package/src/schema-utils.ts +119 -0
- package/src/schemas/Action.ts +40 -0
- package/src/schemas/App.ts +43 -0
- package/src/schemas/Auth.ts +62 -0
- package/src/schemas/Field.ts +34 -0
- package/src/sdk.ts +153 -19
- package/src/types/domain.ts +54 -0
- package/src/types/properties.ts +67 -0
- package/src/types/sdk.ts +42 -0
- package/dist/actions-sdk.d.ts +0 -51
- package/dist/actions-sdk.js +0 -1194
- package/dist/output-schemas.d.ts +0 -95
- package/dist/output-schemas.js +0 -138
- package/dist/schemas.d.ts +0 -338
- package/dist/schemas.js +0 -336
- package/dist/types.js +0 -41
- package/src/actions-sdk.ts +0 -1685
- package/src/output-schemas.ts +0 -196
- package/src/schemas.ts +0 -467
package/src/actions-sdk.ts
DELETED
|
@@ -1,1685 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Integration,
|
|
3
|
-
Action,
|
|
4
|
-
ActionExecutionOptions,
|
|
5
|
-
ActionExecutionResult,
|
|
6
|
-
ActionField,
|
|
7
|
-
NeedsRequest,
|
|
8
|
-
NeedsResponse,
|
|
9
|
-
Authentication,
|
|
10
|
-
AuthenticationsResponse,
|
|
11
|
-
AppNotFoundError,
|
|
12
|
-
BaseSdkOptions,
|
|
13
|
-
} from "./types";
|
|
14
|
-
import {
|
|
15
|
-
AppsListOptions,
|
|
16
|
-
AppsGetOptions,
|
|
17
|
-
ActionsListOptions,
|
|
18
|
-
ActionsGetOptions,
|
|
19
|
-
ActionsRunOptions,
|
|
20
|
-
AuthsListOptions,
|
|
21
|
-
AuthsFindOptions,
|
|
22
|
-
FieldsListOptions,
|
|
23
|
-
GenerateOptions,
|
|
24
|
-
BundleOptions,
|
|
25
|
-
SdkSchemas,
|
|
26
|
-
} from "./schemas";
|
|
27
|
-
export * from "./schemas";
|
|
28
|
-
export * from "./output-schemas";
|
|
29
|
-
|
|
30
|
-
export interface ActionsSdk {
|
|
31
|
-
// Resource namespaces
|
|
32
|
-
apps: AppsService;
|
|
33
|
-
actions: ActionsService;
|
|
34
|
-
auths: AuthsService;
|
|
35
|
-
fields: FieldsService;
|
|
36
|
-
|
|
37
|
-
// Root namespace tools
|
|
38
|
-
generate: GenerateFunction;
|
|
39
|
-
bundle: BundleFunction;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface AppsService {
|
|
43
|
-
list(options?: AppsListOptions): Promise<Integration[]>;
|
|
44
|
-
get(options: AppsGetOptions): Promise<Integration>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface ActionRunner {
|
|
48
|
-
(params: ActionsRunOptions): Promise<ActionExecutionResult>;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface ActionProxy {
|
|
52
|
-
[app: string]: {
|
|
53
|
-
[type: string]: {
|
|
54
|
-
[action: string]: (
|
|
55
|
-
options?: ActionExecutionOptions,
|
|
56
|
-
) => Promise<ActionExecutionResult>;
|
|
57
|
-
};
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface GenerateFunction {
|
|
62
|
-
(options: GenerateOptions): Promise<string>;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface BundleFunction {
|
|
66
|
-
(options: BundleOptions): Promise<string>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface ActionsService {
|
|
70
|
-
list(options?: ActionsListOptions): Promise<Action[]>;
|
|
71
|
-
get(options: ActionsGetOptions): Promise<Action>;
|
|
72
|
-
/** @deprecated Use sdk.fields.list() instead */
|
|
73
|
-
fields(options: FieldsListOptions): Promise<ActionField[]>;
|
|
74
|
-
/** @deprecated Use sdk.fields.list() instead */
|
|
75
|
-
getFields(options: FieldsListOptions): Promise<ActionField[]>;
|
|
76
|
-
run: ActionRunner & ActionProxy;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface AuthsService {
|
|
80
|
-
list(options?: AuthsListOptions): Promise<Authentication[]>;
|
|
81
|
-
find(options: AuthsFindOptions): number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface FieldsService {
|
|
85
|
-
list(options: FieldsListOptions): Promise<ActionField[]>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface ActionsSdkOptions extends BaseSdkOptions {}
|
|
89
|
-
|
|
90
|
-
export function createActionsSdk(options: ActionsSdkOptions = {}): ActionsSdk {
|
|
91
|
-
// Auto-load .env files (searches up directory tree)
|
|
92
|
-
try {
|
|
93
|
-
const { findUpSync } = require("find-up");
|
|
94
|
-
const envPath = findUpSync(".env");
|
|
95
|
-
if (envPath) {
|
|
96
|
-
require("dotenv").config({ path: envPath, quiet: true });
|
|
97
|
-
}
|
|
98
|
-
} catch {
|
|
99
|
-
// Silently fail if dotenv/find-up not available or .env not found
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const {
|
|
103
|
-
fetch: customFetch = globalThis.fetch,
|
|
104
|
-
baseUrl = "https://zapier.com",
|
|
105
|
-
token,
|
|
106
|
-
authentications = {},
|
|
107
|
-
debug = false,
|
|
108
|
-
} = options;
|
|
109
|
-
|
|
110
|
-
// If no token provided, try to get it from environment variable
|
|
111
|
-
const finalToken = token || process.env.ZAPIER_TOKEN;
|
|
112
|
-
|
|
113
|
-
const debugLog = createDebugLogger(debug);
|
|
114
|
-
|
|
115
|
-
// Wrap fetch with debug logging if debug is enabled
|
|
116
|
-
const wrappedFetch = debug
|
|
117
|
-
? createDebugFetch({
|
|
118
|
-
originalFetch: customFetch,
|
|
119
|
-
debugLog,
|
|
120
|
-
})
|
|
121
|
-
: customFetch;
|
|
122
|
-
|
|
123
|
-
// Create SDK object - we'll populate services after creation to avoid circular dependency
|
|
124
|
-
const sdk: ActionsSdk = {} as ActionsSdk;
|
|
125
|
-
|
|
126
|
-
const appsService = createAppsService({
|
|
127
|
-
fetch: wrappedFetch,
|
|
128
|
-
baseUrl,
|
|
129
|
-
token: finalToken,
|
|
130
|
-
authentications,
|
|
131
|
-
debugLog: debug ? debugLog : undefined,
|
|
132
|
-
sdk,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const actionsService = createActionsService({
|
|
136
|
-
fetch: wrappedFetch,
|
|
137
|
-
baseUrl,
|
|
138
|
-
token: finalToken,
|
|
139
|
-
authentications,
|
|
140
|
-
debugLog,
|
|
141
|
-
sdk,
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const authsService = createAuthsService({
|
|
145
|
-
fetch: wrappedFetch,
|
|
146
|
-
baseUrl,
|
|
147
|
-
token: finalToken,
|
|
148
|
-
authentications,
|
|
149
|
-
debugLog,
|
|
150
|
-
sdk,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const fieldsService = createFieldsService({
|
|
154
|
-
fetch: wrappedFetch,
|
|
155
|
-
baseUrl,
|
|
156
|
-
token: finalToken,
|
|
157
|
-
authentications,
|
|
158
|
-
debugLog,
|
|
159
|
-
sdk,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Create root namespace tools
|
|
163
|
-
const generateFunction: GenerateFunction = async (
|
|
164
|
-
options: GenerateOptions,
|
|
165
|
-
) => {
|
|
166
|
-
// Validate options using schema
|
|
167
|
-
const validatedOptions = SdkSchemas.generate.parse(options);
|
|
168
|
-
|
|
169
|
-
return await generateTypes(validatedOptions, sdk);
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const bundleFunction: BundleFunction = async (options: BundleOptions) => {
|
|
173
|
-
// Validate options using schema
|
|
174
|
-
const validatedOptions = SdkSchemas.bundle.parse(options);
|
|
175
|
-
|
|
176
|
-
return await bundleCode(validatedOptions);
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// Populate the SDK object
|
|
180
|
-
sdk.apps = appsService;
|
|
181
|
-
sdk.actions = actionsService;
|
|
182
|
-
sdk.auths = authsService;
|
|
183
|
-
sdk.fields = fieldsService;
|
|
184
|
-
sdk.generate = generateFunction;
|
|
185
|
-
sdk.bundle = bundleFunction;
|
|
186
|
-
|
|
187
|
-
if (debug) {
|
|
188
|
-
debugLog("🚀 Zapier SDK initialized", {
|
|
189
|
-
baseUrl,
|
|
190
|
-
hasAuth: !!finalToken,
|
|
191
|
-
tokenType: finalToken ? (isJwt(finalToken) ? "JWT" : "Bearer") : "none",
|
|
192
|
-
appAuthCount: authentications ? Object.keys(authentications).length : 0,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return sdk;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
interface AppsServiceOptions {
|
|
200
|
-
fetch: typeof globalThis.fetch;
|
|
201
|
-
baseUrl: string;
|
|
202
|
-
token?: string;
|
|
203
|
-
authentications?: { [appKey: string]: { id: number }[] };
|
|
204
|
-
debugLog?: (message: string, details?: any) => void;
|
|
205
|
-
sdk: ActionsSdk;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
interface AuthsServiceOptions {
|
|
209
|
-
fetch: typeof globalThis.fetch;
|
|
210
|
-
baseUrl: string;
|
|
211
|
-
token?: string;
|
|
212
|
-
authentications?: { [appKey: string]: { id: number }[] };
|
|
213
|
-
debugLog?: (message: string, details?: any) => void;
|
|
214
|
-
sdk: ActionsSdk;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
interface ActionsServiceOptions {
|
|
218
|
-
fetch: typeof globalThis.fetch;
|
|
219
|
-
baseUrl: string;
|
|
220
|
-
token?: string;
|
|
221
|
-
authentications?: { [appKey: string]: { id: number }[] };
|
|
222
|
-
debugLog?: (message: string, data?: any) => void;
|
|
223
|
-
sdk: ActionsSdk;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
interface FieldsServiceOptions {
|
|
227
|
-
fetch: typeof globalThis.fetch;
|
|
228
|
-
baseUrl: string;
|
|
229
|
-
token?: string;
|
|
230
|
-
authentications?: { [appKey: string]: { id: number }[] };
|
|
231
|
-
debugLog?: (message: string, data?: any) => void;
|
|
232
|
-
sdk: ActionsSdk;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function createAppsService(options: AppsServiceOptions): AppsService {
|
|
236
|
-
const { fetch, baseUrl, debugLog } = options;
|
|
237
|
-
|
|
238
|
-
return {
|
|
239
|
-
async list(options?: AppsListOptions): Promise<Integration[]> {
|
|
240
|
-
const url = new URL("/api/v4/apps/", baseUrl);
|
|
241
|
-
|
|
242
|
-
// Add query parameters if provided
|
|
243
|
-
if (options?.category) {
|
|
244
|
-
url.searchParams.set("category", options.category);
|
|
245
|
-
}
|
|
246
|
-
if (options?.limit) {
|
|
247
|
-
url.searchParams.set("limit", options.limit.toString());
|
|
248
|
-
}
|
|
249
|
-
if (options?.offset) {
|
|
250
|
-
url.searchParams.set("offset", options.offset.toString());
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const response = await fetch(url.toString());
|
|
254
|
-
if (!response.ok) {
|
|
255
|
-
throw new Error(
|
|
256
|
-
`Failed to fetch apps: ${response.status} ${response.statusText}`,
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const data = await response.json();
|
|
261
|
-
|
|
262
|
-
// Debug: Log pagination info if debug is enabled
|
|
263
|
-
if (debugLog) {
|
|
264
|
-
debugLog("API Response Pagination Info", {
|
|
265
|
-
resultsCount: data.results?.length || 0,
|
|
266
|
-
hasNext: !!data.next,
|
|
267
|
-
hasPrevious: !!data.previous,
|
|
268
|
-
count: data.count,
|
|
269
|
-
nextUrl: data.next,
|
|
270
|
-
previousUrl: data.previous,
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Transform API response to our Integration interface
|
|
275
|
-
const apps =
|
|
276
|
-
data.results?.map(
|
|
277
|
-
(app: any): Integration => ({
|
|
278
|
-
key: app.slug,
|
|
279
|
-
name: app.name,
|
|
280
|
-
description: app.description,
|
|
281
|
-
version: "1.0.0", // API doesn't provide version
|
|
282
|
-
category: app.category?.name,
|
|
283
|
-
actions: [], // Will be populated separately
|
|
284
|
-
triggers: [],
|
|
285
|
-
current_implementation_id: app.current_implementation_id,
|
|
286
|
-
}),
|
|
287
|
-
) || [];
|
|
288
|
-
|
|
289
|
-
// Add pagination metadata to the response
|
|
290
|
-
if (apps.length > 0) {
|
|
291
|
-
(apps as any).__pagination = {
|
|
292
|
-
count: data.count,
|
|
293
|
-
hasNext: !!data.next,
|
|
294
|
-
hasPrevious: !!data.previous,
|
|
295
|
-
nextUrl: data.next,
|
|
296
|
-
previousUrl: data.previous,
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return apps;
|
|
301
|
-
},
|
|
302
|
-
|
|
303
|
-
async get(options: AppsGetOptions): Promise<Integration> {
|
|
304
|
-
const { key } = options;
|
|
305
|
-
const url = new URL(`/api/v4/apps/${key}/`, baseUrl);
|
|
306
|
-
|
|
307
|
-
const response = await fetch(url.toString());
|
|
308
|
-
if (!response.ok) {
|
|
309
|
-
if (response.status === 404) {
|
|
310
|
-
throw new AppNotFoundError(key);
|
|
311
|
-
}
|
|
312
|
-
throw new Error(
|
|
313
|
-
`Failed to fetch app: ${response.status} ${response.statusText}`,
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const app = await response.json();
|
|
318
|
-
|
|
319
|
-
return {
|
|
320
|
-
key: app.slug,
|
|
321
|
-
name: app.name,
|
|
322
|
-
description: app.description,
|
|
323
|
-
version: "1.0.0",
|
|
324
|
-
category: app.category?.name,
|
|
325
|
-
actions: [],
|
|
326
|
-
triggers: [],
|
|
327
|
-
current_implementation_id: app.current_implementation_id,
|
|
328
|
-
};
|
|
329
|
-
},
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function createAuthsService(options: AuthsServiceOptions): AuthsService {
|
|
334
|
-
const { fetch, baseUrl, token, authentications, debugLog, sdk } = options;
|
|
335
|
-
|
|
336
|
-
// Local function to handle the actual API fetching
|
|
337
|
-
const listAuths = async (
|
|
338
|
-
options?: AuthsListOptions,
|
|
339
|
-
): Promise<Authentication[]> => {
|
|
340
|
-
const url = new URL("/api/v4/authentications/", baseUrl);
|
|
341
|
-
|
|
342
|
-
// Handle appKey filtering by getting the selected_api first
|
|
343
|
-
if (options?.appKey) {
|
|
344
|
-
try {
|
|
345
|
-
// Use the SDK's getApp function instead of duplicating the API call
|
|
346
|
-
const appData = await sdk.apps.get({ key: options.appKey });
|
|
347
|
-
const selectedApi = appData.current_implementation_id;
|
|
348
|
-
if (selectedApi) {
|
|
349
|
-
// Use versionless_selected_api to find auths across all app versions
|
|
350
|
-
// Extract the base name without the version (e.g., "SlackCLIAPI" from "SlackCLIAPI@1.21.1")
|
|
351
|
-
const versionlessApi = selectedApi.split("@")[0];
|
|
352
|
-
url.searchParams.set("versionless_selected_api", versionlessApi);
|
|
353
|
-
}
|
|
354
|
-
} catch (error) {
|
|
355
|
-
// If it's an AppNotFoundError, re-throw it
|
|
356
|
-
if (error instanceof AppNotFoundError) {
|
|
357
|
-
throw error;
|
|
358
|
-
}
|
|
359
|
-
// For other errors, continue without app filtering
|
|
360
|
-
console.warn(
|
|
361
|
-
`Warning: Could not filter by app ${options.appKey}:`,
|
|
362
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Add other query parameters if provided
|
|
368
|
-
if (options?.account_id) {
|
|
369
|
-
url.searchParams.set("account_id", options.account_id);
|
|
370
|
-
}
|
|
371
|
-
if (options?.owner) {
|
|
372
|
-
url.searchParams.set("owner", options.owner);
|
|
373
|
-
}
|
|
374
|
-
if (options?.limit) {
|
|
375
|
-
url.searchParams.set("limit", options.limit.toString());
|
|
376
|
-
}
|
|
377
|
-
if (options?.offset) {
|
|
378
|
-
url.searchParams.set("offset", options.offset.toString());
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Build headers with auth if available
|
|
382
|
-
const headers: Record<string, string> = {};
|
|
383
|
-
|
|
384
|
-
if (token) {
|
|
385
|
-
headers["Authorization"] = getAuthorizationHeader(token);
|
|
386
|
-
} else {
|
|
387
|
-
throw new Error(
|
|
388
|
-
"Authentication token is required to list authentications. Please provide token when creating the SDK.",
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const response = await fetch(url.toString(), { headers });
|
|
393
|
-
if (!response.ok) {
|
|
394
|
-
if (response.status === 401) {
|
|
395
|
-
throw new Error(
|
|
396
|
-
`Authentication failed. Your token may not have permission to access authentications or may be expired. (HTTP ${response.status})`,
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
if (response.status === 403) {
|
|
400
|
-
throw new Error(
|
|
401
|
-
`Access forbidden. Your token may not have the required scopes to list authentications. (HTTP ${response.status})`,
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
throw new Error(
|
|
405
|
-
`Failed to fetch authentications: ${response.status} ${response.statusText}`,
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const data: AuthenticationsResponse = await response.json();
|
|
410
|
-
|
|
411
|
-
// Debug: Log pagination info if debug is enabled
|
|
412
|
-
if (debugLog) {
|
|
413
|
-
debugLog("API Response Pagination Info", {
|
|
414
|
-
resultsCount: data.results?.length || 0,
|
|
415
|
-
hasNext: !!data.next,
|
|
416
|
-
hasPrevious: !!data.previous,
|
|
417
|
-
count: data.count,
|
|
418
|
-
nextUrl: data.next,
|
|
419
|
-
previousUrl: data.previous,
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Transform API response
|
|
424
|
-
const auths = data.results || [];
|
|
425
|
-
|
|
426
|
-
// Add pagination metadata to the response
|
|
427
|
-
if (auths.length > 0) {
|
|
428
|
-
(auths as any).__pagination = {
|
|
429
|
-
count: data.count,
|
|
430
|
-
hasNext: !!data.next,
|
|
431
|
-
hasPrevious: !!data.previous,
|
|
432
|
-
nextUrl: data.next,
|
|
433
|
-
previousUrl: data.previous,
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return auths;
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
return {
|
|
441
|
-
async list(options?: AuthsListOptions): Promise<Authentication[]> {
|
|
442
|
-
// If a limit is provided and no specific owner filter, prioritize owned auths
|
|
443
|
-
if (options?.limit && options?.owner === undefined) {
|
|
444
|
-
// First get owned auths
|
|
445
|
-
const ownedAuths = await listAuths({
|
|
446
|
-
...options,
|
|
447
|
-
owner: "me",
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
// If we have enough owned auths, just slice and return
|
|
451
|
-
if (ownedAuths.length >= options.limit) {
|
|
452
|
-
return ownedAuths.slice(0, options.limit);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Get all auths up to the original limit to fill remaining slots
|
|
456
|
-
const allAuths = await listAuths({
|
|
457
|
-
...options,
|
|
458
|
-
owner: undefined,
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// Filter out auths the user already owns to avoid duplicates
|
|
462
|
-
const ownedAuthIds = new Set(ownedAuths.map((auth) => auth.id));
|
|
463
|
-
const additionalAuths = allAuths.filter(
|
|
464
|
-
(auth) => !ownedAuthIds.has(auth.id),
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
// Combine and slice to the requested limit
|
|
468
|
-
const combined = [...ownedAuths, ...additionalAuths];
|
|
469
|
-
return combined.slice(0, options.limit);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Standard implementation for non-prioritized requests
|
|
473
|
-
return listAuths(options);
|
|
474
|
-
},
|
|
475
|
-
|
|
476
|
-
find(options: { appKey: string }): number {
|
|
477
|
-
if (!authentications) {
|
|
478
|
-
throw new Error(`No authentication configured`);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const auths = authentications[options.appKey] || [];
|
|
482
|
-
|
|
483
|
-
if (auths.length === 0) {
|
|
484
|
-
throw new Error(
|
|
485
|
-
`No authentication configured for app "${options.appKey}"`,
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (auths.length > 1) {
|
|
490
|
-
throw new Error(
|
|
491
|
-
`Multiple authentications found for app "${options.appKey}". Please specify which one to use.`,
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return auths[0].id;
|
|
496
|
-
},
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function createFieldsService(options: FieldsServiceOptions): FieldsService {
|
|
501
|
-
const { fetch, baseUrl, token, sdk } = options;
|
|
502
|
-
|
|
503
|
-
return {
|
|
504
|
-
async list(options: FieldsListOptions): Promise<ActionField[]> {
|
|
505
|
-
// Map consistent parameter names to internal API names
|
|
506
|
-
const { app, action, type, authId, params } = options;
|
|
507
|
-
const appKey = app;
|
|
508
|
-
const actionKey = action;
|
|
509
|
-
const actionType = type;
|
|
510
|
-
|
|
511
|
-
// Build headers with auth if available
|
|
512
|
-
const headers: Record<string, string> = {};
|
|
513
|
-
if (token) {
|
|
514
|
-
headers["Authorization"] = getAuthorizationHeader(token);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Use the SDK's getApp function instead of duplicating the API call
|
|
518
|
-
const appData = await sdk.apps.get({ key: appKey });
|
|
519
|
-
const selectedApi = appData.current_implementation_id;
|
|
520
|
-
|
|
521
|
-
if (!selectedApi) {
|
|
522
|
-
throw new Error("No current_implementation_id found for app");
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Build needs request
|
|
526
|
-
const needsRequest: NeedsRequest = {
|
|
527
|
-
selected_api: selectedApi,
|
|
528
|
-
action: actionKey,
|
|
529
|
-
type_of: actionType,
|
|
530
|
-
authentication_id: authId,
|
|
531
|
-
params: params || {},
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
const needsUrl = new URL("/api/v4/implementations/needs/", baseUrl);
|
|
535
|
-
|
|
536
|
-
// Add Content-Type to headers
|
|
537
|
-
const needsHeaders = { ...headers, "Content-Type": "application/json" };
|
|
538
|
-
|
|
539
|
-
const needsResponse = await fetch(needsUrl.toString(), {
|
|
540
|
-
method: "POST",
|
|
541
|
-
headers: needsHeaders,
|
|
542
|
-
body: JSON.stringify(needsRequest),
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
if (!needsResponse.ok) {
|
|
546
|
-
// Add more detailed error info for debugging
|
|
547
|
-
let errorMessage = `Failed to fetch action fields: ${needsResponse.status} ${needsResponse.statusText}`;
|
|
548
|
-
try {
|
|
549
|
-
const errorBody = await needsResponse.text();
|
|
550
|
-
errorMessage += ` - ${errorBody}`;
|
|
551
|
-
} catch {
|
|
552
|
-
// Ignore error reading response body
|
|
553
|
-
}
|
|
554
|
-
throw new Error(errorMessage);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const needsData: NeedsResponse = await needsResponse.json();
|
|
558
|
-
|
|
559
|
-
if (!needsData.success) {
|
|
560
|
-
throw new Error(
|
|
561
|
-
`Failed to get action fields: ${
|
|
562
|
-
needsData.errors?.join(", ") || "Unknown error"
|
|
563
|
-
}`,
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Transform API response to our ActionField interface
|
|
568
|
-
return (needsData.needs || []).map((need: any) => ({
|
|
569
|
-
key: need.key,
|
|
570
|
-
label: need.label,
|
|
571
|
-
required: need.required || false,
|
|
572
|
-
type: need.type,
|
|
573
|
-
helpText: need.help_text,
|
|
574
|
-
helpTextHtml: need.help_text_html,
|
|
575
|
-
choices: need.choices?.map((choice: any) => ({
|
|
576
|
-
value: choice.value,
|
|
577
|
-
label: choice.label,
|
|
578
|
-
})),
|
|
579
|
-
default: need.default,
|
|
580
|
-
placeholder: need.placeholder,
|
|
581
|
-
computed: need.computed,
|
|
582
|
-
customField: need.custom_field,
|
|
583
|
-
dependsOn: need.depends_on,
|
|
584
|
-
format: need.format,
|
|
585
|
-
inputFormat: need.input_format,
|
|
586
|
-
}));
|
|
587
|
-
},
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function createActionsService(options: ActionsServiceOptions): ActionsService {
|
|
592
|
-
const { fetch, baseUrl, token, authentications, debugLog, sdk } = options;
|
|
593
|
-
|
|
594
|
-
// Create the base service with named methods
|
|
595
|
-
const baseService: ActionsService = {
|
|
596
|
-
async list(options?: ActionsListOptions): Promise<Action[]> {
|
|
597
|
-
// If filtering by appKey, use optimized approach with selected_apis parameter
|
|
598
|
-
if (options?.appKey) {
|
|
599
|
-
try {
|
|
600
|
-
// Use the SDK's getApp function instead of duplicating the API call
|
|
601
|
-
const appData = await sdk.apps.get({ key: options.appKey });
|
|
602
|
-
|
|
603
|
-
// Extract implementation name from current_implementation_id (e.g., "SlackCLIAPI@1.20.0" -> "SlackCLIAPI")
|
|
604
|
-
const implementationId =
|
|
605
|
-
appData.current_implementation_id?.split("@")[0];
|
|
606
|
-
if (!implementationId) {
|
|
607
|
-
throw new Error("No current_implementation_id found for app");
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const url = new URL("/api/v4/implementations/", baseUrl);
|
|
611
|
-
url.searchParams.set("global", "true");
|
|
612
|
-
url.searchParams.set("public_only", "true");
|
|
613
|
-
url.searchParams.set("selected_apis", implementationId);
|
|
614
|
-
|
|
615
|
-
const response = await fetch(url.toString());
|
|
616
|
-
if (!response.ok) {
|
|
617
|
-
throw new Error(
|
|
618
|
-
`Failed to fetch actions: ${response.status} ${response.statusText}`,
|
|
619
|
-
);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
const data = await response.json();
|
|
623
|
-
const actions: Action[] = [];
|
|
624
|
-
|
|
625
|
-
// Transform implementations to actions
|
|
626
|
-
for (const implementation of data.results || []) {
|
|
627
|
-
if (implementation.actions) {
|
|
628
|
-
for (const action of implementation.actions) {
|
|
629
|
-
const transformedAction: Action = {
|
|
630
|
-
key: action.key,
|
|
631
|
-
name: action.name || action.label,
|
|
632
|
-
description: action.description || "",
|
|
633
|
-
appKey: implementation.slug || "",
|
|
634
|
-
type: action.type || action.type_of || "read",
|
|
635
|
-
inputFields: [], // Would need additional API call for detailed fields
|
|
636
|
-
outputFields: [],
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
// Apply type filter if provided
|
|
640
|
-
if (options?.type && transformedAction.type !== options.type) {
|
|
641
|
-
continue;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
actions.push(transformedAction);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// Add pagination metadata for app-specific queries
|
|
650
|
-
if (actions.length > 0) {
|
|
651
|
-
(actions as any).__pagination = {
|
|
652
|
-
count: data.count,
|
|
653
|
-
hasNext: !!data.next,
|
|
654
|
-
hasPrevious: !!data.previous,
|
|
655
|
-
nextUrl: data.next,
|
|
656
|
-
previousUrl: data.previous,
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
return actions;
|
|
661
|
-
} catch (error) {
|
|
662
|
-
// If it's an AppNotFoundError, don't try fallback - just re-throw
|
|
663
|
-
if (error instanceof AppNotFoundError) {
|
|
664
|
-
throw error;
|
|
665
|
-
}
|
|
666
|
-
// Fallback to original approach if optimized approach fails
|
|
667
|
-
console.warn(
|
|
668
|
-
"Optimized app lookup failed, falling back to full scan:",
|
|
669
|
-
error,
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Original approach for general queries or when optimization fails
|
|
675
|
-
const url = new URL("/api/v4/implementations/", baseUrl);
|
|
676
|
-
|
|
677
|
-
// Add query parameters
|
|
678
|
-
url.searchParams.set("global", "true");
|
|
679
|
-
url.searchParams.set("public_only", "true");
|
|
680
|
-
|
|
681
|
-
const response = await fetch(url.toString());
|
|
682
|
-
if (!response.ok) {
|
|
683
|
-
throw new Error(
|
|
684
|
-
`Failed to fetch actions: ${response.status} ${response.statusText}`,
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const data = await response.json();
|
|
689
|
-
const actions: Action[] = [];
|
|
690
|
-
|
|
691
|
-
// Transform implementations to actions
|
|
692
|
-
for (const implementation of data.results || []) {
|
|
693
|
-
if (implementation.actions) {
|
|
694
|
-
for (const action of implementation.actions) {
|
|
695
|
-
const transformedAction: Action = {
|
|
696
|
-
key: action.key,
|
|
697
|
-
name: action.name || action.label,
|
|
698
|
-
description: action.description || "",
|
|
699
|
-
appKey: implementation.slug || "",
|
|
700
|
-
type: action.type || action.type_of || "read",
|
|
701
|
-
inputFields: [], // Would need additional API call for detailed fields
|
|
702
|
-
outputFields: [],
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
// Apply filters
|
|
706
|
-
if (
|
|
707
|
-
options?.appKey &&
|
|
708
|
-
transformedAction.appKey !== options.appKey
|
|
709
|
-
) {
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
if (options?.type && transformedAction.type !== options.type) {
|
|
713
|
-
continue;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
actions.push(transformedAction);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// Add pagination metadata for general queries
|
|
722
|
-
if (actions.length > 0) {
|
|
723
|
-
(actions as any).__pagination = {
|
|
724
|
-
count: data.count,
|
|
725
|
-
hasNext: !!data.next,
|
|
726
|
-
hasPrevious: !!data.previous,
|
|
727
|
-
nextUrl: data.next,
|
|
728
|
-
previousUrl: data.previous,
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
return actions;
|
|
733
|
-
},
|
|
734
|
-
|
|
735
|
-
async get(options: ActionsGetOptions): Promise<Action> {
|
|
736
|
-
const { app, action: actionKey, type } = options;
|
|
737
|
-
const actions = await baseService.list({ appKey: app });
|
|
738
|
-
const action = actions.find(
|
|
739
|
-
(a) => a.key === actionKey && a.type === type,
|
|
740
|
-
);
|
|
741
|
-
|
|
742
|
-
if (!action) {
|
|
743
|
-
throw new Error(`Action not found: ${actionKey} with type ${type}`);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
return action;
|
|
747
|
-
},
|
|
748
|
-
|
|
749
|
-
async fields(options: FieldsListOptions): Promise<ActionField[]> {
|
|
750
|
-
return await sdk.fields.list(options);
|
|
751
|
-
},
|
|
752
|
-
|
|
753
|
-
async getFields(options: FieldsListOptions): Promise<ActionField[]> {
|
|
754
|
-
return await sdk.fields.list(options);
|
|
755
|
-
},
|
|
756
|
-
|
|
757
|
-
// run will be replaced with function/proxy pattern below
|
|
758
|
-
} as ActionsService;
|
|
759
|
-
|
|
760
|
-
// Create the run function with proxy capabilities
|
|
761
|
-
const runFunction = async (
|
|
762
|
-
params: ActionsRunOptions,
|
|
763
|
-
): Promise<ActionExecutionResult> => {
|
|
764
|
-
const { app, type, action, inputs, authId: providedAuthId } = params;
|
|
765
|
-
|
|
766
|
-
// Validate that the action exists
|
|
767
|
-
const actionData = await baseService.get({
|
|
768
|
-
app: app,
|
|
769
|
-
action: action,
|
|
770
|
-
type: type,
|
|
771
|
-
});
|
|
772
|
-
|
|
773
|
-
// Validate action type matches
|
|
774
|
-
if (actionData.type !== type) {
|
|
775
|
-
throw new Error(
|
|
776
|
-
`Action type mismatch: expected ${type}, got ${actionData.type}`,
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// Resolve auth ID for this specific app
|
|
781
|
-
let authId: number | undefined = providedAuthId;
|
|
782
|
-
|
|
783
|
-
if (!authId && authentications) {
|
|
784
|
-
const auths = authentications[app] || [];
|
|
785
|
-
if (auths.length === 0) {
|
|
786
|
-
throw new Error(`No authentication configured for app "${app}"`);
|
|
787
|
-
}
|
|
788
|
-
if (auths.length > 1) {
|
|
789
|
-
throw new Error(
|
|
790
|
-
`Multiple authentications found for app "${app}". Please specify which one to use.`,
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
authId = auths[0].id;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Execute the action using the appropriate API based on action type
|
|
797
|
-
const startTime = Date.now();
|
|
798
|
-
const result = await executeActionWithStrategy({
|
|
799
|
-
fetch,
|
|
800
|
-
baseUrl,
|
|
801
|
-
appSlug: app,
|
|
802
|
-
actionKey: action,
|
|
803
|
-
actionType: actionData.type,
|
|
804
|
-
executionOptions: { inputs: inputs || {} },
|
|
805
|
-
auth: token ? { token: token, authentication_id: authId } : undefined,
|
|
806
|
-
debugLog,
|
|
807
|
-
sdk,
|
|
808
|
-
});
|
|
809
|
-
const executionTime = Date.now() - startTime;
|
|
810
|
-
|
|
811
|
-
return {
|
|
812
|
-
success: true,
|
|
813
|
-
data: result,
|
|
814
|
-
metadata: {
|
|
815
|
-
executionTime,
|
|
816
|
-
requestId: generateRequestId(),
|
|
817
|
-
},
|
|
818
|
-
};
|
|
819
|
-
};
|
|
820
|
-
|
|
821
|
-
// Create proxy wrapper for the run function
|
|
822
|
-
const runWithProxy = new Proxy(runFunction, {
|
|
823
|
-
get(target, prop: string) {
|
|
824
|
-
if (typeof prop === "string") {
|
|
825
|
-
// Return app-level proxy
|
|
826
|
-
return new Proxy(
|
|
827
|
-
{},
|
|
828
|
-
{
|
|
829
|
-
get(_target, actionType: string) {
|
|
830
|
-
if (typeof actionType !== "string") return undefined;
|
|
831
|
-
|
|
832
|
-
return new Proxy(
|
|
833
|
-
{},
|
|
834
|
-
{
|
|
835
|
-
get(_target, actionKey: string) {
|
|
836
|
-
if (typeof actionKey !== "string") return undefined;
|
|
837
|
-
|
|
838
|
-
// Return function that calls the main run function
|
|
839
|
-
return async (
|
|
840
|
-
options: ActionExecutionOptions = {},
|
|
841
|
-
): Promise<ActionExecutionResult> => {
|
|
842
|
-
return await runFunction({
|
|
843
|
-
app: prop,
|
|
844
|
-
type: actionType as any, // Cast because proxy string can't be typed as enum
|
|
845
|
-
action: actionKey,
|
|
846
|
-
inputs: options.inputs,
|
|
847
|
-
authId: options.authId,
|
|
848
|
-
});
|
|
849
|
-
};
|
|
850
|
-
},
|
|
851
|
-
},
|
|
852
|
-
);
|
|
853
|
-
},
|
|
854
|
-
},
|
|
855
|
-
);
|
|
856
|
-
}
|
|
857
|
-
return target[prop];
|
|
858
|
-
},
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
// Add the run function/proxy to the base service
|
|
862
|
-
baseService.run = runWithProxy as ActionRunner & ActionProxy;
|
|
863
|
-
|
|
864
|
-
// Return the service with the new run function/proxy
|
|
865
|
-
return baseService;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
interface ExecuteActionStrategyOptions {
|
|
869
|
-
fetch: typeof globalThis.fetch;
|
|
870
|
-
baseUrl: string;
|
|
871
|
-
appSlug: string;
|
|
872
|
-
actionKey: string;
|
|
873
|
-
actionType: string;
|
|
874
|
-
executionOptions: ActionExecutionOptions;
|
|
875
|
-
auth?: { token: string; authentication_id?: number };
|
|
876
|
-
debugLog?: (message: string, data?: any) => void;
|
|
877
|
-
sdk: ActionsSdk;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
async function executeActionWithStrategy(
|
|
881
|
-
options: ExecuteActionStrategyOptions,
|
|
882
|
-
): Promise<any> {
|
|
883
|
-
const {
|
|
884
|
-
fetch,
|
|
885
|
-
baseUrl,
|
|
886
|
-
appSlug,
|
|
887
|
-
actionKey,
|
|
888
|
-
actionType,
|
|
889
|
-
executionOptions,
|
|
890
|
-
auth,
|
|
891
|
-
debugLog,
|
|
892
|
-
sdk,
|
|
893
|
-
} = options;
|
|
894
|
-
debugLog?.("🎯 Choosing execution strategy", { actionType });
|
|
895
|
-
|
|
896
|
-
// Actions API supports: read, read_bulk, write
|
|
897
|
-
// Invoke API supports: search, read, write, read_bulk, and more
|
|
898
|
-
|
|
899
|
-
const actionsApiTypes = ["read", "read_bulk", "write"];
|
|
900
|
-
const useActionsApi = actionsApiTypes.includes(actionType);
|
|
901
|
-
|
|
902
|
-
if (useActionsApi) {
|
|
903
|
-
debugLog?.("🔧 Using Actions API", {
|
|
904
|
-
actionType,
|
|
905
|
-
reason: "supported type",
|
|
906
|
-
});
|
|
907
|
-
return executeActionViaActionsApi({
|
|
908
|
-
fetch,
|
|
909
|
-
baseUrl,
|
|
910
|
-
appSlug,
|
|
911
|
-
actionKey,
|
|
912
|
-
actionType,
|
|
913
|
-
executionOptions,
|
|
914
|
-
auth,
|
|
915
|
-
debugLog,
|
|
916
|
-
sdk,
|
|
917
|
-
});
|
|
918
|
-
} else {
|
|
919
|
-
debugLog?.("🔧 Using Invoke API", {
|
|
920
|
-
actionType,
|
|
921
|
-
reason: "unsupported by Actions API",
|
|
922
|
-
});
|
|
923
|
-
return executeActionViaInvokeApi({
|
|
924
|
-
fetch,
|
|
925
|
-
baseUrl,
|
|
926
|
-
appSlug,
|
|
927
|
-
actionKey,
|
|
928
|
-
actionType,
|
|
929
|
-
executionOptions,
|
|
930
|
-
auth,
|
|
931
|
-
debugLog,
|
|
932
|
-
sdk,
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
interface ExecuteActionViaActionsApiOptions {
|
|
938
|
-
fetch: typeof globalThis.fetch;
|
|
939
|
-
baseUrl: string;
|
|
940
|
-
appSlug: string;
|
|
941
|
-
actionKey: string;
|
|
942
|
-
actionType: string;
|
|
943
|
-
executionOptions: ActionExecutionOptions;
|
|
944
|
-
auth?: { token: string; authentication_id?: number };
|
|
945
|
-
debugLog?: (message: string, data?: any) => void;
|
|
946
|
-
sdk: ActionsSdk;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
async function executeActionViaActionsApi(
|
|
950
|
-
options: ExecuteActionViaActionsApiOptions,
|
|
951
|
-
): Promise<any> {
|
|
952
|
-
const {
|
|
953
|
-
fetch,
|
|
954
|
-
baseUrl,
|
|
955
|
-
appSlug,
|
|
956
|
-
actionKey,
|
|
957
|
-
actionType,
|
|
958
|
-
executionOptions,
|
|
959
|
-
auth,
|
|
960
|
-
sdk,
|
|
961
|
-
} = options;
|
|
962
|
-
// Note: Action validation is handled by the caller before reaching this function
|
|
963
|
-
|
|
964
|
-
// Use the SDK's getApp function instead of duplicating the API call
|
|
965
|
-
const appData = await sdk.apps.get({ key: appSlug });
|
|
966
|
-
const selectedApi = appData.current_implementation_id;
|
|
967
|
-
|
|
968
|
-
if (!selectedApi) {
|
|
969
|
-
throw new Error("No current_implementation_id found for app");
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
if (!auth?.token) {
|
|
973
|
-
throw new Error(
|
|
974
|
-
"Authentication token is required. Please provide token when creating the SDK.",
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
// Determine if token is JWT and use appropriate prefix
|
|
979
|
-
const authHeader = getAuthorizationHeader(auth.token);
|
|
980
|
-
|
|
981
|
-
// Step 1: POST to /actions/v1/runs to start execution
|
|
982
|
-
const runRequest = {
|
|
983
|
-
data: {
|
|
984
|
-
authentication_id: auth.authentication_id || 1,
|
|
985
|
-
selected_api: selectedApi,
|
|
986
|
-
action_key: actionKey,
|
|
987
|
-
action_type: actionType,
|
|
988
|
-
inputs: executionOptions.inputs || {},
|
|
989
|
-
},
|
|
990
|
-
};
|
|
991
|
-
|
|
992
|
-
const runUrl = `${baseUrl}/api/actions/v1/runs`;
|
|
993
|
-
|
|
994
|
-
const headers = {
|
|
995
|
-
"Content-Type": "application/json",
|
|
996
|
-
Authorization: authHeader,
|
|
997
|
-
};
|
|
998
|
-
|
|
999
|
-
const runResponse = await fetch(runUrl, {
|
|
1000
|
-
method: "POST",
|
|
1001
|
-
headers,
|
|
1002
|
-
body: JSON.stringify(runRequest),
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
if (!runResponse.ok) {
|
|
1006
|
-
throw new Error(
|
|
1007
|
-
`Failed to start action execution: POST ${runUrl} - ${runResponse.status} ${runResponse.statusText}`,
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
const runData = await runResponse.json();
|
|
1012
|
-
const runId = runData.data.id;
|
|
1013
|
-
|
|
1014
|
-
// Step 2: Poll GET /actions/v1/runs/{run_id} for results
|
|
1015
|
-
return await pollForResults({ fetch, baseUrl, runId, authHeader });
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
interface PollForResultsOptions {
|
|
1019
|
-
fetch: typeof globalThis.fetch;
|
|
1020
|
-
baseUrl: string;
|
|
1021
|
-
runId: string;
|
|
1022
|
-
authHeader: string;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
async function pollForResults(options: PollForResultsOptions): Promise<any> {
|
|
1026
|
-
const { fetch, baseUrl, runId, authHeader } = options;
|
|
1027
|
-
let delay = 50; // Start with 50ms
|
|
1028
|
-
const maxDelay = 1000; // Cap at 1 second
|
|
1029
|
-
const maxAttempts = 30; // Maximum number of polling attempts
|
|
1030
|
-
|
|
1031
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1032
|
-
const pollUrl = `${baseUrl}/api/actions/v1/runs/${runId}`;
|
|
1033
|
-
|
|
1034
|
-
const response = await fetch(pollUrl, {
|
|
1035
|
-
headers: {
|
|
1036
|
-
Authorization: authHeader,
|
|
1037
|
-
},
|
|
1038
|
-
});
|
|
1039
|
-
|
|
1040
|
-
if (response.status === 200) {
|
|
1041
|
-
// Action completed
|
|
1042
|
-
const result = await response.json();
|
|
1043
|
-
return result.data;
|
|
1044
|
-
} else if (response.status === 202) {
|
|
1045
|
-
// Still processing, wait and try again
|
|
1046
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1047
|
-
delay = Math.min(delay * 2, maxDelay); // Double delay up to max
|
|
1048
|
-
continue;
|
|
1049
|
-
} else {
|
|
1050
|
-
// Error occurred
|
|
1051
|
-
throw new Error(
|
|
1052
|
-
`Failed to fetch action results: GET ${pollUrl} - ${response.status} ${response.statusText}`,
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
throw new Error(`Action execution timed out after ${maxAttempts} attempts`);
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
interface ExecuteActionViaInvokeApiOptions {
|
|
1061
|
-
fetch: typeof globalThis.fetch;
|
|
1062
|
-
baseUrl: string;
|
|
1063
|
-
appSlug: string;
|
|
1064
|
-
actionKey: string;
|
|
1065
|
-
actionType: string;
|
|
1066
|
-
executionOptions: ActionExecutionOptions;
|
|
1067
|
-
auth?: { token: string; authentication_id?: number };
|
|
1068
|
-
debugLog?: (message: string, data?: any) => void;
|
|
1069
|
-
sdk: ActionsSdk;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
async function executeActionViaInvokeApi(
|
|
1073
|
-
options: ExecuteActionViaInvokeApiOptions,
|
|
1074
|
-
): Promise<any> {
|
|
1075
|
-
const {
|
|
1076
|
-
fetch,
|
|
1077
|
-
baseUrl,
|
|
1078
|
-
appSlug,
|
|
1079
|
-
actionKey,
|
|
1080
|
-
actionType,
|
|
1081
|
-
executionOptions,
|
|
1082
|
-
auth,
|
|
1083
|
-
sdk,
|
|
1084
|
-
} = options;
|
|
1085
|
-
// Note: Action and app validation is handled by the caller before reaching this function
|
|
1086
|
-
|
|
1087
|
-
// Use the SDK's getApp function instead of duplicating the API call
|
|
1088
|
-
const appData = await sdk.apps.get({ key: appSlug });
|
|
1089
|
-
const selectedApi = appData.current_implementation_id;
|
|
1090
|
-
|
|
1091
|
-
if (!selectedApi) {
|
|
1092
|
-
throw new Error("No current_implementation_id found for app");
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
if (!auth?.token) {
|
|
1096
|
-
throw new Error(
|
|
1097
|
-
"Authentication token is required. Please provide token when creating the SDK.",
|
|
1098
|
-
);
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
// Determine if token is JWT and use appropriate prefix
|
|
1102
|
-
const authHeader = getAuthorizationHeader(auth.token);
|
|
1103
|
-
|
|
1104
|
-
// Step 1: POST to /invoke/v1/invoke to start execution
|
|
1105
|
-
const invokeRequest = {
|
|
1106
|
-
selected_api: selectedApi,
|
|
1107
|
-
action: actionKey,
|
|
1108
|
-
type_of: actionType,
|
|
1109
|
-
authentication_id: auth.authentication_id || 1,
|
|
1110
|
-
params: executionOptions.inputs || {},
|
|
1111
|
-
};
|
|
1112
|
-
|
|
1113
|
-
const invokeUrl = `${baseUrl}/api/invoke/v1/invoke`;
|
|
1114
|
-
|
|
1115
|
-
const headers = {
|
|
1116
|
-
"Content-Type": "application/json",
|
|
1117
|
-
Authorization: authHeader,
|
|
1118
|
-
};
|
|
1119
|
-
|
|
1120
|
-
const invokeResponse = await fetch(invokeUrl, {
|
|
1121
|
-
method: "POST",
|
|
1122
|
-
headers,
|
|
1123
|
-
body: JSON.stringify(invokeRequest),
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
if (!invokeResponse.ok) {
|
|
1127
|
-
throw new Error(
|
|
1128
|
-
`Failed to start action execution: POST ${invokeUrl} - ${invokeResponse.status} ${invokeResponse.statusText}`,
|
|
1129
|
-
);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
const invokeData = await invokeResponse.json();
|
|
1133
|
-
const invocationId = invokeData.invocation_id;
|
|
1134
|
-
|
|
1135
|
-
// Step 2: Poll GET /invoke/v1/invoke/{invocation_id} for results
|
|
1136
|
-
return await pollForInvokeResults({
|
|
1137
|
-
fetch,
|
|
1138
|
-
baseUrl,
|
|
1139
|
-
invocationId,
|
|
1140
|
-
authHeader,
|
|
1141
|
-
});
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
interface PollForInvokeResultsOptions {
|
|
1145
|
-
fetch: typeof globalThis.fetch;
|
|
1146
|
-
baseUrl: string;
|
|
1147
|
-
invocationId: string;
|
|
1148
|
-
authHeader: string;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
async function pollForInvokeResults(
|
|
1152
|
-
options: PollForInvokeResultsOptions,
|
|
1153
|
-
): Promise<any> {
|
|
1154
|
-
const { fetch, baseUrl, invocationId, authHeader } = options;
|
|
1155
|
-
let delay = 50; // Start with 50ms
|
|
1156
|
-
const maxDelay = 1000; // Cap at 1 second
|
|
1157
|
-
const maxAttempts = 30; // Maximum number of polling attempts
|
|
1158
|
-
|
|
1159
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1160
|
-
const pollUrl = `${baseUrl}/api/invoke/v1/invoke/${invocationId}`;
|
|
1161
|
-
|
|
1162
|
-
const response = await fetch(pollUrl, {
|
|
1163
|
-
headers: {
|
|
1164
|
-
Authorization: authHeader,
|
|
1165
|
-
},
|
|
1166
|
-
});
|
|
1167
|
-
|
|
1168
|
-
if (response.status === 200) {
|
|
1169
|
-
// Action completed
|
|
1170
|
-
const result = await response.json();
|
|
1171
|
-
return result.results || result;
|
|
1172
|
-
} else if (response.status === 202) {
|
|
1173
|
-
// Still processing, wait and try again
|
|
1174
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1175
|
-
delay = Math.min(delay * 2, maxDelay); // Double delay up to max
|
|
1176
|
-
continue;
|
|
1177
|
-
} else {
|
|
1178
|
-
// Error occurred
|
|
1179
|
-
throw new Error(
|
|
1180
|
-
`Failed to fetch action results: GET ${pollUrl} - ${response.status} ${response.statusText}`,
|
|
1181
|
-
);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
throw new Error(`Action execution timed out after ${maxAttempts} attempts`);
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
function generateRequestId(): string {
|
|
1189
|
-
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
function getAuthorizationHeader(token: string): string {
|
|
1193
|
-
// Check if token is a JWT (has 3 parts separated by dots)
|
|
1194
|
-
if (isJwt(token)) {
|
|
1195
|
-
return `JWT ${token}`;
|
|
1196
|
-
}
|
|
1197
|
-
// Default to Bearer for other token types
|
|
1198
|
-
return `Bearer ${token}`;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
function isJwt(token: string): boolean {
|
|
1202
|
-
// JWT tokens have exactly 3 parts separated by dots
|
|
1203
|
-
const parts = token.split(".");
|
|
1204
|
-
if (parts.length !== 3) {
|
|
1205
|
-
return false;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
// Each part should be base64url encoded (no padding)
|
|
1209
|
-
// Basic validation - each part should be non-empty and contain valid base64url characters
|
|
1210
|
-
const base64urlPattern = /^[A-Za-z0-9_-]+$/;
|
|
1211
|
-
return parts.every((part) => part.length > 0 && base64urlPattern.test(part));
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
function createDebugLogger(enabled: boolean) {
|
|
1215
|
-
if (!enabled) {
|
|
1216
|
-
return () => {}; // No-op function when debug is disabled
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
return (message: string, data?: any) => {
|
|
1220
|
-
const timestamp = new Date().toISOString();
|
|
1221
|
-
if (data) {
|
|
1222
|
-
console.log(`[${timestamp}] Zapier SDK: ${message}`, data);
|
|
1223
|
-
} else {
|
|
1224
|
-
console.log(`[${timestamp}] Zapier SDK: ${message}`);
|
|
1225
|
-
}
|
|
1226
|
-
};
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
interface DebugFetchOptions {
|
|
1230
|
-
originalFetch: typeof globalThis.fetch;
|
|
1231
|
-
debugLog: (message: string, data?: any) => void;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
function createDebugFetch(options: DebugFetchOptions) {
|
|
1235
|
-
const { originalFetch, debugLog } = options;
|
|
1236
|
-
return async (input: RequestInfo | URL, options?: RequestInit) => {
|
|
1237
|
-
const startTime = Date.now();
|
|
1238
|
-
|
|
1239
|
-
// Convert input to URL string for logging
|
|
1240
|
-
const urlString =
|
|
1241
|
-
input instanceof URL
|
|
1242
|
-
? input.toString()
|
|
1243
|
-
: typeof input === "string"
|
|
1244
|
-
? input
|
|
1245
|
-
: input.url;
|
|
1246
|
-
|
|
1247
|
-
// Log request
|
|
1248
|
-
const requestInfo = {
|
|
1249
|
-
method: options?.method || "GET",
|
|
1250
|
-
url: urlString,
|
|
1251
|
-
headers: options?.headers
|
|
1252
|
-
? maskSensitiveHeaders(options.headers)
|
|
1253
|
-
: undefined,
|
|
1254
|
-
hasBody: !!options?.body,
|
|
1255
|
-
};
|
|
1256
|
-
|
|
1257
|
-
debugLog("📤 HTTP Request", requestInfo);
|
|
1258
|
-
|
|
1259
|
-
try {
|
|
1260
|
-
const response = await originalFetch(input, options);
|
|
1261
|
-
const duration = Date.now() - startTime;
|
|
1262
|
-
|
|
1263
|
-
// Log response
|
|
1264
|
-
debugLog("📥 HTTP Response", {
|
|
1265
|
-
status: response.status,
|
|
1266
|
-
statusText: response.statusText,
|
|
1267
|
-
ok: response.ok,
|
|
1268
|
-
duration: `${duration}ms`,
|
|
1269
|
-
url: urlString,
|
|
1270
|
-
});
|
|
1271
|
-
|
|
1272
|
-
return response;
|
|
1273
|
-
} catch (error) {
|
|
1274
|
-
const duration = Date.now() - startTime;
|
|
1275
|
-
|
|
1276
|
-
debugLog("❌ HTTP Error", {
|
|
1277
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1278
|
-
duration: `${duration}ms`,
|
|
1279
|
-
url: urlString,
|
|
1280
|
-
});
|
|
1281
|
-
|
|
1282
|
-
throw error;
|
|
1283
|
-
}
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
function maskSensitiveHeaders(headers: HeadersInit): Record<string, string> {
|
|
1288
|
-
const maskedHeaders: Record<string, string> = {};
|
|
1289
|
-
|
|
1290
|
-
if (headers instanceof Headers) {
|
|
1291
|
-
headers.forEach((value, key) => {
|
|
1292
|
-
maskedHeaders[key] =
|
|
1293
|
-
key.toLowerCase() === "authorization" ? maskToken(value) : value;
|
|
1294
|
-
});
|
|
1295
|
-
} else if (Array.isArray(headers)) {
|
|
1296
|
-
headers.forEach(([key, value]) => {
|
|
1297
|
-
maskedHeaders[key] =
|
|
1298
|
-
key.toLowerCase() === "authorization" ? maskToken(value) : value;
|
|
1299
|
-
});
|
|
1300
|
-
} else {
|
|
1301
|
-
Object.entries(headers).forEach(([key, value]) => {
|
|
1302
|
-
maskedHeaders[key] =
|
|
1303
|
-
key.toLowerCase() === "authorization" ? maskToken(value) : value;
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
return maskedHeaders;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
function maskToken(token: string): string {
|
|
1311
|
-
if (token.length <= 8) {
|
|
1312
|
-
return "***";
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
// Show first 4 and last 4 characters, mask the middle
|
|
1316
|
-
const start = token.substring(0, 4);
|
|
1317
|
-
const end = token.substring(token.length - 4);
|
|
1318
|
-
const middle = "*".repeat(Math.min(token.length - 8, 20)); // Cap mask length
|
|
1319
|
-
|
|
1320
|
-
return `${start}${middle}${end}`;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
// ============================================================================
|
|
1324
|
-
// Generate Implementation
|
|
1325
|
-
// ============================================================================
|
|
1326
|
-
|
|
1327
|
-
interface ActionWithActionFields extends Omit<Action, "inputFields"> {
|
|
1328
|
-
inputFields: ActionField[];
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
async function generateTypes(
|
|
1332
|
-
options: GenerateOptions,
|
|
1333
|
-
sdk: ActionsSdk,
|
|
1334
|
-
): Promise<string> {
|
|
1335
|
-
const { appKey, authId, output = `./types/${appKey}.d.ts` } = options;
|
|
1336
|
-
|
|
1337
|
-
// Parse app identifier (support app@version format)
|
|
1338
|
-
const { app, version } = parseAppIdentifier(appKey);
|
|
1339
|
-
|
|
1340
|
-
// Fetch all actions for the app
|
|
1341
|
-
const actions = await sdk.actions.list({ appKey: app });
|
|
1342
|
-
|
|
1343
|
-
if (actions.length === 0) {
|
|
1344
|
-
const typeDefinitions = generateEmptyTypesFile(app, version);
|
|
1345
|
-
|
|
1346
|
-
if (output) {
|
|
1347
|
-
const fs = await import("fs");
|
|
1348
|
-
const path = await import("path");
|
|
1349
|
-
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
1350
|
-
fs.writeFileSync(output, typeDefinitions, "utf8");
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
return typeDefinitions;
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
// Fetch input fields for each action
|
|
1357
|
-
const actionsWithFields: ActionWithActionFields[] = [];
|
|
1358
|
-
|
|
1359
|
-
if (authId) {
|
|
1360
|
-
for (const action of actions) {
|
|
1361
|
-
try {
|
|
1362
|
-
const fields = await sdk.fields.list({
|
|
1363
|
-
app: action.appKey,
|
|
1364
|
-
action: action.key,
|
|
1365
|
-
type: action.type,
|
|
1366
|
-
authId: authId,
|
|
1367
|
-
});
|
|
1368
|
-
actionsWithFields.push({ ...action, inputFields: fields });
|
|
1369
|
-
} catch {
|
|
1370
|
-
// If we can't get fields for an action, include it without fields
|
|
1371
|
-
actionsWithFields.push({ ...action, inputFields: [] });
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
} else {
|
|
1375
|
-
// Convert actions to have empty input fields (will generate generic types)
|
|
1376
|
-
actions.forEach((action) => {
|
|
1377
|
-
actionsWithFields.push({ ...action, inputFields: [] });
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// Generate TypeScript types
|
|
1382
|
-
const typeDefinitions = generateTypeDefinitions(
|
|
1383
|
-
app,
|
|
1384
|
-
actionsWithFields,
|
|
1385
|
-
version,
|
|
1386
|
-
);
|
|
1387
|
-
|
|
1388
|
-
// Write to file if output path specified
|
|
1389
|
-
if (output) {
|
|
1390
|
-
const fs = await import("fs");
|
|
1391
|
-
const path = await import("path");
|
|
1392
|
-
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
1393
|
-
fs.writeFileSync(output, typeDefinitions, "utf8");
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
return typeDefinitions;
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
function parseAppIdentifier(identifier: string): {
|
|
1400
|
-
app: string;
|
|
1401
|
-
version?: string;
|
|
1402
|
-
} {
|
|
1403
|
-
const parts = identifier.split("@");
|
|
1404
|
-
return {
|
|
1405
|
-
app: parts[0],
|
|
1406
|
-
version: parts[1],
|
|
1407
|
-
};
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
function generateTypeDefinitions(
|
|
1411
|
-
appKey: string,
|
|
1412
|
-
actions: ActionWithActionFields[],
|
|
1413
|
-
version?: string,
|
|
1414
|
-
): string {
|
|
1415
|
-
// Handle empty actions
|
|
1416
|
-
if (actions.length === 0) {
|
|
1417
|
-
return generateEmptyTypesFile(appKey, version);
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
// Group actions by type
|
|
1421
|
-
const actionsByType = actions.reduce(
|
|
1422
|
-
(acc, action) => {
|
|
1423
|
-
if (!acc[action.type]) {
|
|
1424
|
-
acc[action.type] = [];
|
|
1425
|
-
}
|
|
1426
|
-
acc[action.type].push(action);
|
|
1427
|
-
return acc;
|
|
1428
|
-
},
|
|
1429
|
-
{} as Record<string, ActionWithActionFields[]>,
|
|
1430
|
-
);
|
|
1431
|
-
|
|
1432
|
-
const appName = capitalize(appKey);
|
|
1433
|
-
const versionComment = version
|
|
1434
|
-
? ` * Generated for ${appKey}@${version}`
|
|
1435
|
-
: ` * Generated for ${appKey}`;
|
|
1436
|
-
|
|
1437
|
-
let output = `/* eslint-disable @typescript-eslint/naming-convention */
|
|
1438
|
-
/**
|
|
1439
|
-
* Auto-generated TypeScript types for Zapier ${appKey} actions
|
|
1440
|
-
${versionComment}
|
|
1441
|
-
* Generated on: ${new Date().toISOString()}
|
|
1442
|
-
*
|
|
1443
|
-
* Usage:
|
|
1444
|
-
* import type { ${appName}Actions } from './path/to/this/file'
|
|
1445
|
-
* const sdk: ActionsSdk & { actions: ${appName}Actions } = createActionsSdk(...)
|
|
1446
|
-
*/
|
|
1447
|
-
|
|
1448
|
-
import type { ActionExecutionOptions, ActionExecutionResult } from '@zapier/zapier-sdk'
|
|
1449
|
-
|
|
1450
|
-
`;
|
|
1451
|
-
|
|
1452
|
-
// Generate input types for each action
|
|
1453
|
-
actions.forEach((action) => {
|
|
1454
|
-
if (action.inputFields.length > 0) {
|
|
1455
|
-
const inputTypeName = `${appName}${capitalize(action.type)}${capitalize(
|
|
1456
|
-
sanitizeActionName(action.key),
|
|
1457
|
-
)}Inputs`;
|
|
1458
|
-
|
|
1459
|
-
output += `interface ${inputTypeName} {\n`;
|
|
1460
|
-
|
|
1461
|
-
action.inputFields.forEach((field) => {
|
|
1462
|
-
const isOptional = !field.required;
|
|
1463
|
-
const fieldType = mapFieldTypeToTypeScript(field);
|
|
1464
|
-
const description = field.helpText
|
|
1465
|
-
? ` /** ${escapeComment(field.helpText)} */\n`
|
|
1466
|
-
: "";
|
|
1467
|
-
|
|
1468
|
-
output += `${description} ${sanitizeFieldName(field.key)}${
|
|
1469
|
-
isOptional ? "?" : ""
|
|
1470
|
-
}: ${fieldType}\n`;
|
|
1471
|
-
});
|
|
1472
|
-
|
|
1473
|
-
output += `}\n\n`;
|
|
1474
|
-
}
|
|
1475
|
-
});
|
|
1476
|
-
|
|
1477
|
-
// Generate action type interfaces for each action type
|
|
1478
|
-
Object.entries(actionsByType).forEach(([actionType, typeActions]) => {
|
|
1479
|
-
const typeName = `${appName}${capitalize(actionType)}Actions`;
|
|
1480
|
-
|
|
1481
|
-
output += `interface ${typeName} {\n`;
|
|
1482
|
-
|
|
1483
|
-
typeActions.forEach((action: ActionWithActionFields) => {
|
|
1484
|
-
const actionName = sanitizeActionName(action.key);
|
|
1485
|
-
const description = action.description
|
|
1486
|
-
? ` /** ${escapeComment(action.description)} */\n`
|
|
1487
|
-
: "";
|
|
1488
|
-
|
|
1489
|
-
// Generate type-safe action method signature
|
|
1490
|
-
if (action.inputFields.length > 0) {
|
|
1491
|
-
const inputTypeName = `${appName}${capitalize(action.type)}${capitalize(
|
|
1492
|
-
sanitizeActionName(action.key),
|
|
1493
|
-
)}Inputs`;
|
|
1494
|
-
output += `${description} ${actionName}: (options: { inputs: ${inputTypeName} } & Omit<ActionExecutionOptions, 'inputs'>) => Promise<ActionExecutionResult>\n`;
|
|
1495
|
-
} else {
|
|
1496
|
-
// No specific input fields available - use generic Record<string, any> for inputs
|
|
1497
|
-
output += `${description} ${actionName}: (options?: { inputs?: Record<string, any> } & ActionExecutionOptions) => Promise<ActionExecutionResult>\n`;
|
|
1498
|
-
}
|
|
1499
|
-
});
|
|
1500
|
-
|
|
1501
|
-
output += `}\n\n`;
|
|
1502
|
-
});
|
|
1503
|
-
|
|
1504
|
-
// Generate the main app actions interface
|
|
1505
|
-
output += `export interface ${appName}Actions {\n`;
|
|
1506
|
-
output += ` run: {\n`;
|
|
1507
|
-
output += ` ${appKey}: {\n`;
|
|
1508
|
-
|
|
1509
|
-
Object.keys(actionsByType).forEach((actionType) => {
|
|
1510
|
-
const typeName = `${appName}${capitalize(actionType)}Actions`;
|
|
1511
|
-
output += ` ${actionType}: ${typeName}\n`;
|
|
1512
|
-
});
|
|
1513
|
-
|
|
1514
|
-
output += ` }\n`;
|
|
1515
|
-
output += ` }\n`;
|
|
1516
|
-
output += `}\n`;
|
|
1517
|
-
|
|
1518
|
-
return output;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
function generateEmptyTypesFile(appKey: string, version?: string): string {
|
|
1522
|
-
const appName = capitalize(appKey);
|
|
1523
|
-
const versionComment = version
|
|
1524
|
-
? ` * Generated for ${appKey}@${version}`
|
|
1525
|
-
: ` * Generated for ${appKey}`;
|
|
1526
|
-
|
|
1527
|
-
return `/* eslint-disable @typescript-eslint/naming-convention */
|
|
1528
|
-
/**
|
|
1529
|
-
* Auto-generated TypeScript types for Zapier ${appKey} actions
|
|
1530
|
-
${versionComment}
|
|
1531
|
-
* Generated on: ${new Date().toISOString()}
|
|
1532
|
-
*
|
|
1533
|
-
* No actions found for this app.
|
|
1534
|
-
*/
|
|
1535
|
-
|
|
1536
|
-
import type { ActionExecutionOptions, ActionExecutionResult } from '@zapier/zapier-sdk'
|
|
1537
|
-
|
|
1538
|
-
export interface ${appName}Actions {
|
|
1539
|
-
run: {
|
|
1540
|
-
${appKey}: {
|
|
1541
|
-
// No actions available
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
`;
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
function capitalize(str: string): string {
|
|
1549
|
-
return str.charAt(0).toUpperCase() + str.slice(1).replace(/[-_]/g, "");
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
function sanitizeActionName(actionKey: string): string {
|
|
1553
|
-
// Ensure the action name is a valid TypeScript identifier
|
|
1554
|
-
return actionKey.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
function sanitizeFieldName(fieldKey: string): string {
|
|
1558
|
-
// Ensure the field name is a valid TypeScript identifier
|
|
1559
|
-
return fieldKey.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
function escapeComment(comment: string): string {
|
|
1563
|
-
// Escape comment text to prevent breaking the JSDoc comment
|
|
1564
|
-
return comment.replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
function mapFieldTypeToTypeScript(field: ActionField): string {
|
|
1568
|
-
// Handle choices (enum-like fields)
|
|
1569
|
-
if (field.choices && field.choices.length > 0) {
|
|
1570
|
-
const choiceValues = field.choices
|
|
1571
|
-
.filter(
|
|
1572
|
-
(choice) =>
|
|
1573
|
-
choice.value !== undefined &&
|
|
1574
|
-
choice.value !== null &&
|
|
1575
|
-
choice.value !== "",
|
|
1576
|
-
)
|
|
1577
|
-
.map((choice) =>
|
|
1578
|
-
typeof choice.value === "string" ? `"${choice.value}"` : choice.value,
|
|
1579
|
-
);
|
|
1580
|
-
|
|
1581
|
-
if (choiceValues.length > 0) {
|
|
1582
|
-
return choiceValues.join(" | ");
|
|
1583
|
-
}
|
|
1584
|
-
// If all choices were filtered out, fall through to default type handling
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
// Map Zapier field types to TypeScript types
|
|
1588
|
-
switch (field.type?.toLowerCase()) {
|
|
1589
|
-
case "string":
|
|
1590
|
-
case "text":
|
|
1591
|
-
case "email":
|
|
1592
|
-
case "url":
|
|
1593
|
-
case "password":
|
|
1594
|
-
return "string";
|
|
1595
|
-
case "integer":
|
|
1596
|
-
case "number":
|
|
1597
|
-
return "number";
|
|
1598
|
-
case "boolean":
|
|
1599
|
-
return "boolean";
|
|
1600
|
-
case "datetime":
|
|
1601
|
-
case "date":
|
|
1602
|
-
return "string"; // ISO date strings
|
|
1603
|
-
case "file":
|
|
1604
|
-
return "string"; // File URL or content
|
|
1605
|
-
case "array":
|
|
1606
|
-
return "any[]";
|
|
1607
|
-
case "object":
|
|
1608
|
-
return "Record<string, any>";
|
|
1609
|
-
default:
|
|
1610
|
-
// Default to string for unknown types, with union for common cases
|
|
1611
|
-
return "string | number | boolean";
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
// ============================================================================
|
|
1616
|
-
// Bundle Implementation
|
|
1617
|
-
// ============================================================================
|
|
1618
|
-
|
|
1619
|
-
async function bundleCode(options: BundleOptions): Promise<string> {
|
|
1620
|
-
const {
|
|
1621
|
-
input,
|
|
1622
|
-
output,
|
|
1623
|
-
target = "es2020",
|
|
1624
|
-
cjs = false,
|
|
1625
|
-
minify = false,
|
|
1626
|
-
string: returnString = false,
|
|
1627
|
-
} = options;
|
|
1628
|
-
|
|
1629
|
-
// Dynamically import esbuild
|
|
1630
|
-
const { buildSync } = await import("esbuild");
|
|
1631
|
-
const fs = await import("fs");
|
|
1632
|
-
const path = await import("path");
|
|
1633
|
-
|
|
1634
|
-
// Resolve input path
|
|
1635
|
-
const resolvedInput = path.resolve(process.cwd(), input);
|
|
1636
|
-
|
|
1637
|
-
try {
|
|
1638
|
-
// Bundle with esbuild
|
|
1639
|
-
const result = buildSync({
|
|
1640
|
-
entryPoints: [resolvedInput],
|
|
1641
|
-
bundle: true,
|
|
1642
|
-
platform: "node",
|
|
1643
|
-
target: target,
|
|
1644
|
-
format: cjs ? "cjs" : "esm",
|
|
1645
|
-
minify: minify,
|
|
1646
|
-
write: false,
|
|
1647
|
-
external: [], // Bundle everything
|
|
1648
|
-
banner: {
|
|
1649
|
-
js: "#!/usr/bin/env node",
|
|
1650
|
-
},
|
|
1651
|
-
});
|
|
1652
|
-
|
|
1653
|
-
if (result.errors.length > 0) {
|
|
1654
|
-
throw new Error(
|
|
1655
|
-
`Bundle failed: ${result.errors.map((e) => e.text).join(", ")}`,
|
|
1656
|
-
);
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
const bundledCode = result.outputFiles?.[0]?.text;
|
|
1660
|
-
if (!bundledCode) {
|
|
1661
|
-
throw new Error("No output generated");
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
let finalOutput = bundledCode;
|
|
1665
|
-
|
|
1666
|
-
if (returnString) {
|
|
1667
|
-
// Output as quoted string for node -e using JSON.stringify
|
|
1668
|
-
finalOutput = JSON.stringify(bundledCode);
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
// Write to file if output path specified
|
|
1672
|
-
if (output) {
|
|
1673
|
-
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
1674
|
-
fs.writeFileSync(output, finalOutput, "utf8");
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
return finalOutput;
|
|
1678
|
-
} catch (error) {
|
|
1679
|
-
throw new Error(
|
|
1680
|
-
`Bundle failed: ${
|
|
1681
|
-
error instanceof Error ? error.message : "Unknown error"
|
|
1682
|
-
}`,
|
|
1683
|
-
);
|
|
1684
|
-
}
|
|
1685
|
-
}
|