@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.
Files changed (167) hide show
  1. package/dist/api/auth.d.ts +8 -0
  2. package/dist/api/auth.js +29 -0
  3. package/dist/api/client.d.ts +8 -0
  4. package/dist/api/client.js +102 -0
  5. package/dist/api/debug.d.ts +12 -0
  6. package/dist/api/debug.js +50 -0
  7. package/dist/api/index.d.ts +26 -0
  8. package/dist/api/index.js +51 -0
  9. package/dist/api/polling.d.ts +17 -0
  10. package/dist/api/polling.js +34 -0
  11. package/dist/{types.d.ts → api/types.d.ts} +37 -76
  12. package/dist/api/types.js +9 -0
  13. package/dist/functions/bundleCode/index.d.ts +11 -0
  14. package/dist/functions/bundleCode/index.js +91 -0
  15. package/dist/functions/bundleCode/info.d.ts +27 -0
  16. package/dist/functions/bundleCode/info.js +11 -0
  17. package/dist/functions/bundleCode/schemas.d.ts +27 -0
  18. package/dist/functions/bundleCode/schemas.js +22 -0
  19. package/dist/functions/generateTypes/index.d.ts +11 -0
  20. package/dist/functions/generateTypes/index.js +305 -0
  21. package/dist/functions/generateTypes/info.d.ts +21 -0
  22. package/dist/functions/generateTypes/info.js +11 -0
  23. package/dist/functions/generateTypes/schemas.d.ts +30 -0
  24. package/dist/functions/generateTypes/schemas.js +14 -0
  25. package/dist/functions/getAction/index.d.ts +12 -0
  26. package/dist/functions/getAction/index.js +26 -0
  27. package/dist/functions/getAction/info.d.ts +18 -0
  28. package/dist/functions/getAction/info.js +11 -0
  29. package/dist/functions/getAction/schemas.d.ts +30 -0
  30. package/dist/functions/getAction/schemas.js +13 -0
  31. package/dist/functions/getApp/index.d.ts +12 -0
  32. package/dist/functions/getApp/index.js +37 -0
  33. package/dist/functions/getApp/info.d.ts +12 -0
  34. package/dist/functions/getApp/info.js +11 -0
  35. package/dist/functions/getApp/schemas.d.ts +24 -0
  36. package/dist/functions/getApp/schemas.js +11 -0
  37. package/dist/functions/listActions/index.d.ts +12 -0
  38. package/dist/functions/listActions/index.js +128 -0
  39. package/dist/functions/listActions/info.d.ts +15 -0
  40. package/dist/functions/listActions/info.js +11 -0
  41. package/dist/functions/listActions/schemas.d.ts +27 -0
  42. package/dist/functions/listActions/schemas.js +14 -0
  43. package/dist/functions/listApps/index.d.ts +12 -0
  44. package/dist/functions/listApps/index.js +50 -0
  45. package/dist/functions/listApps/info.d.ts +18 -0
  46. package/dist/functions/listApps/info.js +11 -0
  47. package/dist/functions/listApps/schemas.d.ts +30 -0
  48. package/dist/functions/listApps/schemas.js +15 -0
  49. package/dist/functions/listAuths/index.d.ts +12 -0
  50. package/dist/functions/listAuths/index.js +118 -0
  51. package/dist/functions/listAuths/info.d.ts +24 -0
  52. package/dist/functions/listAuths/info.js +11 -0
  53. package/dist/functions/listAuths/schemas.d.ts +36 -0
  54. package/dist/functions/listAuths/schemas.js +17 -0
  55. package/dist/functions/listFields/index.d.ts +12 -0
  56. package/dist/functions/listFields/index.js +65 -0
  57. package/dist/functions/listFields/info.d.ts +24 -0
  58. package/dist/functions/listFields/info.js +11 -0
  59. package/dist/functions/listFields/schemas.d.ts +36 -0
  60. package/dist/functions/listFields/schemas.js +17 -0
  61. package/dist/functions/runAction/index.d.ts +12 -0
  62. package/dist/functions/runAction/index.js +157 -0
  63. package/dist/functions/runAction/info.d.ts +24 -0
  64. package/dist/functions/runAction/info.js +11 -0
  65. package/dist/functions/runAction/schemas.d.ts +36 -0
  66. package/dist/functions/runAction/schemas.js +15 -0
  67. package/dist/index.d.ts +15 -3
  68. package/dist/index.js +27 -4
  69. package/dist/plugins/apps/index.d.ts +8 -0
  70. package/dist/plugins/apps/index.js +77 -0
  71. package/dist/plugins/apps/info.d.ts +6 -0
  72. package/dist/plugins/apps/info.js +13 -0
  73. package/dist/plugins/apps/types.d.ts +21 -0
  74. package/dist/plugins/apps/types.js +2 -0
  75. package/dist/resolvers/actionKey.d.ts +8 -0
  76. package/dist/resolvers/actionKey.js +20 -0
  77. package/dist/resolvers/actionType.d.ts +8 -0
  78. package/dist/resolvers/actionType.js +21 -0
  79. package/dist/resolvers/appKey.d.ts +6 -0
  80. package/dist/resolvers/appKey.js +8 -0
  81. package/dist/resolvers/authenticationId.d.ts +8 -0
  82. package/dist/resolvers/authenticationId.js +29 -0
  83. package/dist/resolvers/index.d.ts +39 -0
  84. package/dist/resolvers/index.js +105 -0
  85. package/dist/resolvers/inputs.d.ts +7 -0
  86. package/dist/resolvers/inputs.js +15 -0
  87. package/dist/schema-utils.d.ts +39 -0
  88. package/dist/schema-utils.js +52 -0
  89. package/dist/schemas/Action.d.ts +21 -0
  90. package/dist/schemas/Action.js +31 -0
  91. package/dist/schemas/App.d.ts +19 -0
  92. package/dist/schemas/App.js +32 -0
  93. package/dist/schemas/Auth.d.ts +30 -0
  94. package/dist/schemas/Auth.js +49 -0
  95. package/dist/schemas/Field.d.ts +15 -0
  96. package/dist/schemas/Field.js +25 -0
  97. package/dist/sdk.d.ts +3 -4
  98. package/dist/sdk.js +96 -11
  99. package/dist/types/domain.d.ts +22 -0
  100. package/dist/types/domain.js +21 -0
  101. package/dist/types/properties.d.ts +21 -0
  102. package/dist/types/properties.js +45 -0
  103. package/dist/types/sdk.d.ts +21 -0
  104. package/dist/types/sdk.js +2 -0
  105. package/package.json +4 -2
  106. package/src/api/auth.ts +28 -0
  107. package/src/api/client.ts +148 -0
  108. package/src/api/debug.ts +58 -0
  109. package/src/api/index.ts +83 -0
  110. package/src/api/polling.ts +56 -0
  111. package/src/{types.ts → api/types.ts} +51 -118
  112. package/src/functions/bundleCode/index.ts +78 -0
  113. package/src/functions/bundleCode/info.ts +9 -0
  114. package/src/functions/bundleCode/schemas.ts +30 -0
  115. package/src/functions/generateTypes/index.ts +348 -0
  116. package/src/functions/generateTypes/info.ts +9 -0
  117. package/src/functions/generateTypes/schemas.ts +38 -0
  118. package/src/functions/getAction/index.ts +33 -0
  119. package/src/functions/getAction/info.ts +9 -0
  120. package/src/functions/getAction/schemas.ts +35 -0
  121. package/src/functions/getApp/index.ts +41 -0
  122. package/src/functions/getApp/info.ts +9 -0
  123. package/src/functions/getApp/schemas.ts +31 -0
  124. package/src/functions/listActions/index.ts +149 -0
  125. package/src/functions/listActions/info.ts +9 -0
  126. package/src/functions/listActions/schemas.ts +40 -0
  127. package/src/functions/listApps/index.ts +60 -0
  128. package/src/functions/listApps/info.ts +9 -0
  129. package/src/functions/listApps/schemas.ts +43 -0
  130. package/src/functions/listAuths/index.ts +153 -0
  131. package/src/functions/listAuths/info.ts +9 -0
  132. package/src/functions/listAuths/schemas.ts +48 -0
  133. package/src/functions/listFields/index.ts +86 -0
  134. package/src/functions/listFields/info.ts +9 -0
  135. package/src/functions/listFields/schemas.ts +46 -0
  136. package/src/functions/runAction/index.ts +258 -0
  137. package/src/functions/runAction/info.ts +9 -0
  138. package/src/functions/runAction/schemas.ts +41 -0
  139. package/src/index.ts +24 -4
  140. package/src/plugins/apps/index.ts +144 -0
  141. package/src/plugins/apps/info.ts +12 -0
  142. package/src/plugins/apps/types.ts +34 -0
  143. package/src/resolvers/actionKey.ts +33 -0
  144. package/src/resolvers/actionType.ts +30 -0
  145. package/src/resolvers/appKey.ts +11 -0
  146. package/src/resolvers/authenticationId.ts +38 -0
  147. package/src/resolvers/index.ts +117 -0
  148. package/src/resolvers/inputs.ts +23 -0
  149. package/src/schema-utils.ts +119 -0
  150. package/src/schemas/Action.ts +40 -0
  151. package/src/schemas/App.ts +43 -0
  152. package/src/schemas/Auth.ts +62 -0
  153. package/src/schemas/Field.ts +34 -0
  154. package/src/sdk.ts +153 -19
  155. package/src/types/domain.ts +54 -0
  156. package/src/types/properties.ts +67 -0
  157. package/src/types/sdk.ts +42 -0
  158. package/dist/actions-sdk.d.ts +0 -51
  159. package/dist/actions-sdk.js +0 -1194
  160. package/dist/output-schemas.d.ts +0 -95
  161. package/dist/output-schemas.js +0 -138
  162. package/dist/schemas.d.ts +0 -338
  163. package/dist/schemas.js +0 -336
  164. package/dist/types.js +0 -41
  165. package/src/actions-sdk.ts +0 -1685
  166. package/src/output-schemas.ts +0 -196
  167. package/src/schemas.ts +0 -467
@@ -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
- }