@zapier/zapier-sdk 0.0.1 → 0.0.2

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.
@@ -0,0 +1,1194 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
+ };
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.createActionsSdk = createActionsSdk;
40
+ const types_1 = require("./types");
41
+ const schemas_1 = require("./schemas");
42
+ __exportStar(require("./schemas"), exports);
43
+ __exportStar(require("./output-schemas"), exports);
44
+ function createActionsSdk(options = {}) {
45
+ // Auto-load .env files (searches up directory tree)
46
+ try {
47
+ const { findUpSync } = require("find-up");
48
+ const envPath = findUpSync(".env");
49
+ if (envPath) {
50
+ require("dotenv").config({ path: envPath, quiet: true });
51
+ }
52
+ }
53
+ catch {
54
+ // Silently fail if dotenv/find-up not available or .env not found
55
+ }
56
+ const { fetch: customFetch = globalThis.fetch, baseUrl = "https://zapier.com", token, authentications = {}, debug = false, } = options;
57
+ // If no token provided, try to get it from environment variable
58
+ const finalToken = token || process.env.ZAPIER_TOKEN;
59
+ const debugLog = createDebugLogger(debug);
60
+ // Wrap fetch with debug logging if debug is enabled
61
+ const wrappedFetch = debug
62
+ ? createDebugFetch({
63
+ originalFetch: customFetch,
64
+ debugLog,
65
+ })
66
+ : customFetch;
67
+ // Create SDK object - we'll populate services after creation to avoid circular dependency
68
+ const sdk = {};
69
+ const appsService = createAppsService({
70
+ fetch: wrappedFetch,
71
+ baseUrl,
72
+ token: finalToken,
73
+ authentications,
74
+ debugLog: debug ? debugLog : undefined,
75
+ sdk,
76
+ });
77
+ const actionsService = createActionsService({
78
+ fetch: wrappedFetch,
79
+ baseUrl,
80
+ token: finalToken,
81
+ authentications,
82
+ debugLog,
83
+ sdk,
84
+ });
85
+ const authsService = createAuthsService({
86
+ fetch: wrappedFetch,
87
+ baseUrl,
88
+ token: finalToken,
89
+ authentications,
90
+ debugLog,
91
+ sdk,
92
+ });
93
+ const fieldsService = createFieldsService({
94
+ fetch: wrappedFetch,
95
+ baseUrl,
96
+ token: finalToken,
97
+ authentications,
98
+ debugLog,
99
+ sdk,
100
+ });
101
+ // Create root namespace tools
102
+ const generateFunction = async (options) => {
103
+ // Validate options using schema
104
+ const validatedOptions = schemas_1.SdkSchemas.generate.parse(options);
105
+ return await generateTypes(validatedOptions, sdk);
106
+ };
107
+ const bundleFunction = async (options) => {
108
+ // Validate options using schema
109
+ const validatedOptions = schemas_1.SdkSchemas.bundle.parse(options);
110
+ return await bundleCode(validatedOptions);
111
+ };
112
+ // Populate the SDK object
113
+ sdk.apps = appsService;
114
+ sdk.actions = actionsService;
115
+ sdk.auths = authsService;
116
+ sdk.fields = fieldsService;
117
+ sdk.generate = generateFunction;
118
+ sdk.bundle = bundleFunction;
119
+ if (debug) {
120
+ debugLog("🚀 Zapier SDK initialized", {
121
+ baseUrl,
122
+ hasAuth: !!finalToken,
123
+ tokenType: finalToken ? (isJwt(finalToken) ? "JWT" : "Bearer") : "none",
124
+ appAuthCount: authentications ? Object.keys(authentications).length : 0,
125
+ });
126
+ }
127
+ return sdk;
128
+ }
129
+ function createAppsService(options) {
130
+ const { fetch, baseUrl, debugLog } = options;
131
+ return {
132
+ async list(options) {
133
+ const url = new URL("/api/v4/apps/", baseUrl);
134
+ // Add query parameters if provided
135
+ if (options?.category) {
136
+ url.searchParams.set("category", options.category);
137
+ }
138
+ if (options?.limit) {
139
+ url.searchParams.set("limit", options.limit.toString());
140
+ }
141
+ if (options?.offset) {
142
+ url.searchParams.set("offset", options.offset.toString());
143
+ }
144
+ const response = await fetch(url.toString());
145
+ if (!response.ok) {
146
+ throw new Error(`Failed to fetch apps: ${response.status} ${response.statusText}`);
147
+ }
148
+ const data = await response.json();
149
+ // Debug: Log pagination info if debug is enabled
150
+ if (debugLog) {
151
+ debugLog("API Response Pagination Info", {
152
+ resultsCount: data.results?.length || 0,
153
+ hasNext: !!data.next,
154
+ hasPrevious: !!data.previous,
155
+ count: data.count,
156
+ nextUrl: data.next,
157
+ previousUrl: data.previous,
158
+ });
159
+ }
160
+ // Transform API response to our Integration interface
161
+ const apps = data.results?.map((app) => ({
162
+ key: app.slug,
163
+ name: app.name,
164
+ description: app.description,
165
+ version: "1.0.0", // API doesn't provide version
166
+ category: app.category?.name,
167
+ actions: [], // Will be populated separately
168
+ triggers: [],
169
+ current_implementation_id: app.current_implementation_id,
170
+ })) || [];
171
+ // Add pagination metadata to the response
172
+ if (apps.length > 0) {
173
+ apps.__pagination = {
174
+ count: data.count,
175
+ hasNext: !!data.next,
176
+ hasPrevious: !!data.previous,
177
+ nextUrl: data.next,
178
+ previousUrl: data.previous,
179
+ };
180
+ }
181
+ return apps;
182
+ },
183
+ async get(options) {
184
+ const { key } = options;
185
+ const url = new URL(`/api/v4/apps/${key}/`, baseUrl);
186
+ const response = await fetch(url.toString());
187
+ if (!response.ok) {
188
+ if (response.status === 404) {
189
+ throw new types_1.AppNotFoundError(key);
190
+ }
191
+ throw new Error(`Failed to fetch app: ${response.status} ${response.statusText}`);
192
+ }
193
+ const app = await response.json();
194
+ return {
195
+ key: app.slug,
196
+ name: app.name,
197
+ description: app.description,
198
+ version: "1.0.0",
199
+ category: app.category?.name,
200
+ actions: [],
201
+ triggers: [],
202
+ current_implementation_id: app.current_implementation_id,
203
+ };
204
+ },
205
+ };
206
+ }
207
+ function createAuthsService(options) {
208
+ const { fetch, baseUrl, token, authentications, debugLog, sdk } = options;
209
+ // Local function to handle the actual API fetching
210
+ const listAuths = async (options) => {
211
+ const url = new URL("/api/v4/authentications/", baseUrl);
212
+ // Handle appKey filtering by getting the selected_api first
213
+ if (options?.appKey) {
214
+ try {
215
+ // Use the SDK's getApp function instead of duplicating the API call
216
+ const appData = await sdk.apps.get({ key: options.appKey });
217
+ const selectedApi = appData.current_implementation_id;
218
+ if (selectedApi) {
219
+ // Use versionless_selected_api to find auths across all app versions
220
+ // Extract the base name without the version (e.g., "SlackCLIAPI" from "SlackCLIAPI@1.21.1")
221
+ const versionlessApi = selectedApi.split("@")[0];
222
+ url.searchParams.set("versionless_selected_api", versionlessApi);
223
+ }
224
+ }
225
+ catch (error) {
226
+ // If it's an AppNotFoundError, re-throw it
227
+ if (error instanceof types_1.AppNotFoundError) {
228
+ throw error;
229
+ }
230
+ // For other errors, continue without app filtering
231
+ console.warn(`Warning: Could not filter by app ${options.appKey}:`, error instanceof Error ? error.message : "Unknown error");
232
+ }
233
+ }
234
+ // Add other query parameters if provided
235
+ if (options?.account_id) {
236
+ url.searchParams.set("account_id", options.account_id);
237
+ }
238
+ if (options?.owner) {
239
+ url.searchParams.set("owner", options.owner);
240
+ }
241
+ if (options?.limit) {
242
+ url.searchParams.set("limit", options.limit.toString());
243
+ }
244
+ if (options?.offset) {
245
+ url.searchParams.set("offset", options.offset.toString());
246
+ }
247
+ // Build headers with auth if available
248
+ const headers = {};
249
+ if (token) {
250
+ headers["Authorization"] = getAuthorizationHeader(token);
251
+ }
252
+ else {
253
+ throw new Error("Authentication token is required to list authentications. Please provide token when creating the SDK.");
254
+ }
255
+ const response = await fetch(url.toString(), { headers });
256
+ if (!response.ok) {
257
+ if (response.status === 401) {
258
+ throw new Error(`Authentication failed. Your token may not have permission to access authentications or may be expired. (HTTP ${response.status})`);
259
+ }
260
+ if (response.status === 403) {
261
+ throw new Error(`Access forbidden. Your token may not have the required scopes to list authentications. (HTTP ${response.status})`);
262
+ }
263
+ throw new Error(`Failed to fetch authentications: ${response.status} ${response.statusText}`);
264
+ }
265
+ const data = await response.json();
266
+ // Debug: Log pagination info if debug is enabled
267
+ if (debugLog) {
268
+ debugLog("API Response Pagination Info", {
269
+ resultsCount: data.results?.length || 0,
270
+ hasNext: !!data.next,
271
+ hasPrevious: !!data.previous,
272
+ count: data.count,
273
+ nextUrl: data.next,
274
+ previousUrl: data.previous,
275
+ });
276
+ }
277
+ // Transform API response
278
+ const auths = data.results || [];
279
+ // Add pagination metadata to the response
280
+ if (auths.length > 0) {
281
+ auths.__pagination = {
282
+ count: data.count,
283
+ hasNext: !!data.next,
284
+ hasPrevious: !!data.previous,
285
+ nextUrl: data.next,
286
+ previousUrl: data.previous,
287
+ };
288
+ }
289
+ return auths;
290
+ };
291
+ return {
292
+ async list(options) {
293
+ // If a limit is provided and no specific owner filter, prioritize owned auths
294
+ if (options?.limit && options?.owner === undefined) {
295
+ // First get owned auths
296
+ const ownedAuths = await listAuths({
297
+ ...options,
298
+ owner: "me",
299
+ });
300
+ // If we have enough owned auths, just slice and return
301
+ if (ownedAuths.length >= options.limit) {
302
+ return ownedAuths.slice(0, options.limit);
303
+ }
304
+ // Get all auths up to the original limit to fill remaining slots
305
+ const allAuths = await listAuths({
306
+ ...options,
307
+ owner: undefined,
308
+ });
309
+ // Filter out auths the user already owns to avoid duplicates
310
+ const ownedAuthIds = new Set(ownedAuths.map((auth) => auth.id));
311
+ const additionalAuths = allAuths.filter((auth) => !ownedAuthIds.has(auth.id));
312
+ // Combine and slice to the requested limit
313
+ const combined = [...ownedAuths, ...additionalAuths];
314
+ return combined.slice(0, options.limit);
315
+ }
316
+ // Standard implementation for non-prioritized requests
317
+ return listAuths(options);
318
+ },
319
+ find(options) {
320
+ if (!authentications) {
321
+ throw new Error(`No authentication configured`);
322
+ }
323
+ const auths = authentications[options.appKey] || [];
324
+ if (auths.length === 0) {
325
+ throw new Error(`No authentication configured for app "${options.appKey}"`);
326
+ }
327
+ if (auths.length > 1) {
328
+ throw new Error(`Multiple authentications found for app "${options.appKey}". Please specify which one to use.`);
329
+ }
330
+ return auths[0].id;
331
+ },
332
+ };
333
+ }
334
+ function createFieldsService(options) {
335
+ const { fetch, baseUrl, token, sdk } = options;
336
+ return {
337
+ async list(options) {
338
+ // Map consistent parameter names to internal API names
339
+ const { app, action, type, authId, params } = options;
340
+ const appKey = app;
341
+ const actionKey = action;
342
+ const actionType = type;
343
+ // Build headers with auth if available
344
+ const headers = {};
345
+ if (token) {
346
+ headers["Authorization"] = getAuthorizationHeader(token);
347
+ }
348
+ // Use the SDK's getApp function instead of duplicating the API call
349
+ const appData = await sdk.apps.get({ key: appKey });
350
+ const selectedApi = appData.current_implementation_id;
351
+ if (!selectedApi) {
352
+ throw new Error("No current_implementation_id found for app");
353
+ }
354
+ // Build needs request
355
+ const needsRequest = {
356
+ selected_api: selectedApi,
357
+ action: actionKey,
358
+ type_of: actionType,
359
+ authentication_id: authId,
360
+ params: params || {},
361
+ };
362
+ const needsUrl = new URL("/api/v4/implementations/needs/", baseUrl);
363
+ // Add Content-Type to headers
364
+ const needsHeaders = { ...headers, "Content-Type": "application/json" };
365
+ const needsResponse = await fetch(needsUrl.toString(), {
366
+ method: "POST",
367
+ headers: needsHeaders,
368
+ body: JSON.stringify(needsRequest),
369
+ });
370
+ if (!needsResponse.ok) {
371
+ // Add more detailed error info for debugging
372
+ let errorMessage = `Failed to fetch action fields: ${needsResponse.status} ${needsResponse.statusText}`;
373
+ try {
374
+ const errorBody = await needsResponse.text();
375
+ errorMessage += ` - ${errorBody}`;
376
+ }
377
+ catch {
378
+ // Ignore error reading response body
379
+ }
380
+ throw new Error(errorMessage);
381
+ }
382
+ const needsData = await needsResponse.json();
383
+ if (!needsData.success) {
384
+ throw new Error(`Failed to get action fields: ${needsData.errors?.join(", ") || "Unknown error"}`);
385
+ }
386
+ // Transform API response to our ActionField interface
387
+ return (needsData.needs || []).map((need) => ({
388
+ key: need.key,
389
+ label: need.label,
390
+ required: need.required || false,
391
+ type: need.type,
392
+ helpText: need.help_text,
393
+ helpTextHtml: need.help_text_html,
394
+ choices: need.choices?.map((choice) => ({
395
+ value: choice.value,
396
+ label: choice.label,
397
+ })),
398
+ default: need.default,
399
+ placeholder: need.placeholder,
400
+ computed: need.computed,
401
+ customField: need.custom_field,
402
+ dependsOn: need.depends_on,
403
+ format: need.format,
404
+ inputFormat: need.input_format,
405
+ }));
406
+ },
407
+ };
408
+ }
409
+ function createActionsService(options) {
410
+ const { fetch, baseUrl, token, authentications, debugLog, sdk } = options;
411
+ // Create the base service with named methods
412
+ const baseService = {
413
+ async list(options) {
414
+ // If filtering by appKey, use optimized approach with selected_apis parameter
415
+ if (options?.appKey) {
416
+ try {
417
+ // Use the SDK's getApp function instead of duplicating the API call
418
+ const appData = await sdk.apps.get({ key: options.appKey });
419
+ // Extract implementation name from current_implementation_id (e.g., "SlackCLIAPI@1.20.0" -> "SlackCLIAPI")
420
+ const implementationId = appData.current_implementation_id?.split("@")[0];
421
+ if (!implementationId) {
422
+ throw new Error("No current_implementation_id found for app");
423
+ }
424
+ const url = new URL("/api/v4/implementations/", baseUrl);
425
+ url.searchParams.set("global", "true");
426
+ url.searchParams.set("public_only", "true");
427
+ url.searchParams.set("selected_apis", implementationId);
428
+ const response = await fetch(url.toString());
429
+ if (!response.ok) {
430
+ throw new Error(`Failed to fetch actions: ${response.status} ${response.statusText}`);
431
+ }
432
+ const data = await response.json();
433
+ const actions = [];
434
+ // Transform implementations to actions
435
+ for (const implementation of data.results || []) {
436
+ if (implementation.actions) {
437
+ for (const action of implementation.actions) {
438
+ const transformedAction = {
439
+ key: action.key,
440
+ name: action.name || action.label,
441
+ description: action.description || "",
442
+ appKey: implementation.slug || "",
443
+ type: action.type || action.type_of || "read",
444
+ inputFields: [], // Would need additional API call for detailed fields
445
+ outputFields: [],
446
+ };
447
+ // Apply type filter if provided
448
+ if (options?.type && transformedAction.type !== options.type) {
449
+ continue;
450
+ }
451
+ actions.push(transformedAction);
452
+ }
453
+ }
454
+ }
455
+ // Add pagination metadata for app-specific queries
456
+ if (actions.length > 0) {
457
+ actions.__pagination = {
458
+ count: data.count,
459
+ hasNext: !!data.next,
460
+ hasPrevious: !!data.previous,
461
+ nextUrl: data.next,
462
+ previousUrl: data.previous,
463
+ };
464
+ }
465
+ return actions;
466
+ }
467
+ catch (error) {
468
+ // If it's an AppNotFoundError, don't try fallback - just re-throw
469
+ if (error instanceof types_1.AppNotFoundError) {
470
+ throw error;
471
+ }
472
+ // Fallback to original approach if optimized approach fails
473
+ console.warn("Optimized app lookup failed, falling back to full scan:", error);
474
+ }
475
+ }
476
+ // Original approach for general queries or when optimization fails
477
+ const url = new URL("/api/v4/implementations/", baseUrl);
478
+ // Add query parameters
479
+ url.searchParams.set("global", "true");
480
+ url.searchParams.set("public_only", "true");
481
+ const response = await fetch(url.toString());
482
+ if (!response.ok) {
483
+ throw new Error(`Failed to fetch actions: ${response.status} ${response.statusText}`);
484
+ }
485
+ const data = await response.json();
486
+ const actions = [];
487
+ // Transform implementations to actions
488
+ for (const implementation of data.results || []) {
489
+ if (implementation.actions) {
490
+ for (const action of implementation.actions) {
491
+ const transformedAction = {
492
+ key: action.key,
493
+ name: action.name || action.label,
494
+ description: action.description || "",
495
+ appKey: implementation.slug || "",
496
+ type: action.type || action.type_of || "read",
497
+ inputFields: [], // Would need additional API call for detailed fields
498
+ outputFields: [],
499
+ };
500
+ // Apply filters
501
+ if (options?.appKey &&
502
+ transformedAction.appKey !== options.appKey) {
503
+ continue;
504
+ }
505
+ if (options?.type && transformedAction.type !== options.type) {
506
+ continue;
507
+ }
508
+ actions.push(transformedAction);
509
+ }
510
+ }
511
+ }
512
+ // Add pagination metadata for general queries
513
+ if (actions.length > 0) {
514
+ actions.__pagination = {
515
+ count: data.count,
516
+ hasNext: !!data.next,
517
+ hasPrevious: !!data.previous,
518
+ nextUrl: data.next,
519
+ previousUrl: data.previous,
520
+ };
521
+ }
522
+ return actions;
523
+ },
524
+ async get(options) {
525
+ const { app, action: actionKey, type } = options;
526
+ const actions = await baseService.list({ appKey: app });
527
+ const action = actions.find((a) => a.key === actionKey && a.type === type);
528
+ if (!action) {
529
+ throw new Error(`Action not found: ${actionKey} with type ${type}`);
530
+ }
531
+ return action;
532
+ },
533
+ async fields(options) {
534
+ return await sdk.fields.list(options);
535
+ },
536
+ async getFields(options) {
537
+ return await sdk.fields.list(options);
538
+ },
539
+ // run will be replaced with function/proxy pattern below
540
+ };
541
+ // Create the run function with proxy capabilities
542
+ const runFunction = async (params) => {
543
+ const { app, type, action, inputs, authId: providedAuthId } = params;
544
+ // Validate that the action exists
545
+ const actionData = await baseService.get({
546
+ app: app,
547
+ action: action,
548
+ type: type,
549
+ });
550
+ // Validate action type matches
551
+ if (actionData.type !== type) {
552
+ throw new Error(`Action type mismatch: expected ${type}, got ${actionData.type}`);
553
+ }
554
+ // Resolve auth ID for this specific app
555
+ let authId = providedAuthId;
556
+ if (!authId && authentications) {
557
+ const auths = authentications[app] || [];
558
+ if (auths.length === 0) {
559
+ throw new Error(`No authentication configured for app "${app}"`);
560
+ }
561
+ if (auths.length > 1) {
562
+ throw new Error(`Multiple authentications found for app "${app}". Please specify which one to use.`);
563
+ }
564
+ authId = auths[0].id;
565
+ }
566
+ // Execute the action using the appropriate API based on action type
567
+ const startTime = Date.now();
568
+ const result = await executeActionWithStrategy({
569
+ fetch,
570
+ baseUrl,
571
+ appSlug: app,
572
+ actionKey: action,
573
+ actionType: actionData.type,
574
+ executionOptions: { inputs: inputs || {} },
575
+ auth: token ? { token: token, authentication_id: authId } : undefined,
576
+ debugLog,
577
+ sdk,
578
+ });
579
+ const executionTime = Date.now() - startTime;
580
+ return {
581
+ success: true,
582
+ data: result,
583
+ metadata: {
584
+ executionTime,
585
+ requestId: generateRequestId(),
586
+ },
587
+ };
588
+ };
589
+ // Create proxy wrapper for the run function
590
+ const runWithProxy = new Proxy(runFunction, {
591
+ get(target, prop) {
592
+ if (typeof prop === "string") {
593
+ // Return app-level proxy
594
+ return new Proxy({}, {
595
+ get(_target, actionType) {
596
+ if (typeof actionType !== "string")
597
+ return undefined;
598
+ return new Proxy({}, {
599
+ get(_target, actionKey) {
600
+ if (typeof actionKey !== "string")
601
+ return undefined;
602
+ // Return function that calls the main run function
603
+ return async (options = {}) => {
604
+ return await runFunction({
605
+ app: prop,
606
+ type: actionType, // Cast because proxy string can't be typed as enum
607
+ action: actionKey,
608
+ inputs: options.inputs,
609
+ authId: options.authId,
610
+ });
611
+ };
612
+ },
613
+ });
614
+ },
615
+ });
616
+ }
617
+ return target[prop];
618
+ },
619
+ });
620
+ // Add the run function/proxy to the base service
621
+ baseService.run = runWithProxy;
622
+ // Return the service with the new run function/proxy
623
+ return baseService;
624
+ }
625
+ async function executeActionWithStrategy(options) {
626
+ const { fetch, baseUrl, appSlug, actionKey, actionType, executionOptions, auth, debugLog, sdk, } = options;
627
+ debugLog?.("🎯 Choosing execution strategy", { actionType });
628
+ // Actions API supports: read, read_bulk, write
629
+ // Invoke API supports: search, read, write, read_bulk, and more
630
+ const actionsApiTypes = ["read", "read_bulk", "write"];
631
+ const useActionsApi = actionsApiTypes.includes(actionType);
632
+ if (useActionsApi) {
633
+ debugLog?.("🔧 Using Actions API", {
634
+ actionType,
635
+ reason: "supported type",
636
+ });
637
+ return executeActionViaActionsApi({
638
+ fetch,
639
+ baseUrl,
640
+ appSlug,
641
+ actionKey,
642
+ actionType,
643
+ executionOptions,
644
+ auth,
645
+ debugLog,
646
+ sdk,
647
+ });
648
+ }
649
+ else {
650
+ debugLog?.("🔧 Using Invoke API", {
651
+ actionType,
652
+ reason: "unsupported by Actions API",
653
+ });
654
+ return executeActionViaInvokeApi({
655
+ fetch,
656
+ baseUrl,
657
+ appSlug,
658
+ actionKey,
659
+ actionType,
660
+ executionOptions,
661
+ auth,
662
+ debugLog,
663
+ sdk,
664
+ });
665
+ }
666
+ }
667
+ async function executeActionViaActionsApi(options) {
668
+ const { fetch, baseUrl, appSlug, actionKey, actionType, executionOptions, auth, sdk, } = options;
669
+ // Note: Action validation is handled by the caller before reaching this function
670
+ // Use the SDK's getApp function instead of duplicating the API call
671
+ const appData = await sdk.apps.get({ key: appSlug });
672
+ const selectedApi = appData.current_implementation_id;
673
+ if (!selectedApi) {
674
+ throw new Error("No current_implementation_id found for app");
675
+ }
676
+ if (!auth?.token) {
677
+ throw new Error("Authentication token is required. Please provide token when creating the SDK.");
678
+ }
679
+ // Determine if token is JWT and use appropriate prefix
680
+ const authHeader = getAuthorizationHeader(auth.token);
681
+ // Step 1: POST to /actions/v1/runs to start execution
682
+ const runRequest = {
683
+ data: {
684
+ authentication_id: auth.authentication_id || 1,
685
+ selected_api: selectedApi,
686
+ action_key: actionKey,
687
+ action_type: actionType,
688
+ inputs: executionOptions.inputs || {},
689
+ },
690
+ };
691
+ const runUrl = `${baseUrl}/api/actions/v1/runs`;
692
+ const headers = {
693
+ "Content-Type": "application/json",
694
+ Authorization: authHeader,
695
+ };
696
+ const runResponse = await fetch(runUrl, {
697
+ method: "POST",
698
+ headers,
699
+ body: JSON.stringify(runRequest),
700
+ });
701
+ if (!runResponse.ok) {
702
+ throw new Error(`Failed to start action execution: POST ${runUrl} - ${runResponse.status} ${runResponse.statusText}`);
703
+ }
704
+ const runData = await runResponse.json();
705
+ const runId = runData.data.id;
706
+ // Step 2: Poll GET /actions/v1/runs/{run_id} for results
707
+ return await pollForResults({ fetch, baseUrl, runId, authHeader });
708
+ }
709
+ async function pollForResults(options) {
710
+ const { fetch, baseUrl, runId, authHeader } = options;
711
+ let delay = 50; // Start with 50ms
712
+ const maxDelay = 1000; // Cap at 1 second
713
+ const maxAttempts = 30; // Maximum number of polling attempts
714
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
715
+ const pollUrl = `${baseUrl}/api/actions/v1/runs/${runId}`;
716
+ const response = await fetch(pollUrl, {
717
+ headers: {
718
+ Authorization: authHeader,
719
+ },
720
+ });
721
+ if (response.status === 200) {
722
+ // Action completed
723
+ const result = await response.json();
724
+ return result.data;
725
+ }
726
+ else if (response.status === 202) {
727
+ // Still processing, wait and try again
728
+ await new Promise((resolve) => setTimeout(resolve, delay));
729
+ delay = Math.min(delay * 2, maxDelay); // Double delay up to max
730
+ continue;
731
+ }
732
+ else {
733
+ // Error occurred
734
+ throw new Error(`Failed to fetch action results: GET ${pollUrl} - ${response.status} ${response.statusText}`);
735
+ }
736
+ }
737
+ throw new Error(`Action execution timed out after ${maxAttempts} attempts`);
738
+ }
739
+ async function executeActionViaInvokeApi(options) {
740
+ const { fetch, baseUrl, appSlug, actionKey, actionType, executionOptions, auth, sdk, } = options;
741
+ // Note: Action and app validation is handled by the caller before reaching this function
742
+ // Use the SDK's getApp function instead of duplicating the API call
743
+ const appData = await sdk.apps.get({ key: appSlug });
744
+ const selectedApi = appData.current_implementation_id;
745
+ if (!selectedApi) {
746
+ throw new Error("No current_implementation_id found for app");
747
+ }
748
+ if (!auth?.token) {
749
+ throw new Error("Authentication token is required. Please provide token when creating the SDK.");
750
+ }
751
+ // Determine if token is JWT and use appropriate prefix
752
+ const authHeader = getAuthorizationHeader(auth.token);
753
+ // Step 1: POST to /invoke/v1/invoke to start execution
754
+ const invokeRequest = {
755
+ selected_api: selectedApi,
756
+ action: actionKey,
757
+ type_of: actionType,
758
+ authentication_id: auth.authentication_id || 1,
759
+ params: executionOptions.inputs || {},
760
+ };
761
+ const invokeUrl = `${baseUrl}/api/invoke/v1/invoke`;
762
+ const headers = {
763
+ "Content-Type": "application/json",
764
+ Authorization: authHeader,
765
+ };
766
+ const invokeResponse = await fetch(invokeUrl, {
767
+ method: "POST",
768
+ headers,
769
+ body: JSON.stringify(invokeRequest),
770
+ });
771
+ if (!invokeResponse.ok) {
772
+ throw new Error(`Failed to start action execution: POST ${invokeUrl} - ${invokeResponse.status} ${invokeResponse.statusText}`);
773
+ }
774
+ const invokeData = await invokeResponse.json();
775
+ const invocationId = invokeData.invocation_id;
776
+ // Step 2: Poll GET /invoke/v1/invoke/{invocation_id} for results
777
+ return await pollForInvokeResults({
778
+ fetch,
779
+ baseUrl,
780
+ invocationId,
781
+ authHeader,
782
+ });
783
+ }
784
+ async function pollForInvokeResults(options) {
785
+ const { fetch, baseUrl, invocationId, authHeader } = options;
786
+ let delay = 50; // Start with 50ms
787
+ const maxDelay = 1000; // Cap at 1 second
788
+ const maxAttempts = 30; // Maximum number of polling attempts
789
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
790
+ const pollUrl = `${baseUrl}/api/invoke/v1/invoke/${invocationId}`;
791
+ const response = await fetch(pollUrl, {
792
+ headers: {
793
+ Authorization: authHeader,
794
+ },
795
+ });
796
+ if (response.status === 200) {
797
+ // Action completed
798
+ const result = await response.json();
799
+ return result.results || result;
800
+ }
801
+ else if (response.status === 202) {
802
+ // Still processing, wait and try again
803
+ await new Promise((resolve) => setTimeout(resolve, delay));
804
+ delay = Math.min(delay * 2, maxDelay); // Double delay up to max
805
+ continue;
806
+ }
807
+ else {
808
+ // Error occurred
809
+ throw new Error(`Failed to fetch action results: GET ${pollUrl} - ${response.status} ${response.statusText}`);
810
+ }
811
+ }
812
+ throw new Error(`Action execution timed out after ${maxAttempts} attempts`);
813
+ }
814
+ function generateRequestId() {
815
+ return Math.random().toString(36).substring(2) + Date.now().toString(36);
816
+ }
817
+ function getAuthorizationHeader(token) {
818
+ // Check if token is a JWT (has 3 parts separated by dots)
819
+ if (isJwt(token)) {
820
+ return `JWT ${token}`;
821
+ }
822
+ // Default to Bearer for other token types
823
+ return `Bearer ${token}`;
824
+ }
825
+ function isJwt(token) {
826
+ // JWT tokens have exactly 3 parts separated by dots
827
+ const parts = token.split(".");
828
+ if (parts.length !== 3) {
829
+ return false;
830
+ }
831
+ // Each part should be base64url encoded (no padding)
832
+ // Basic validation - each part should be non-empty and contain valid base64url characters
833
+ const base64urlPattern = /^[A-Za-z0-9_-]+$/;
834
+ return parts.every((part) => part.length > 0 && base64urlPattern.test(part));
835
+ }
836
+ function createDebugLogger(enabled) {
837
+ if (!enabled) {
838
+ return () => { }; // No-op function when debug is disabled
839
+ }
840
+ return (message, data) => {
841
+ const timestamp = new Date().toISOString();
842
+ if (data) {
843
+ console.log(`[${timestamp}] Zapier SDK: ${message}`, data);
844
+ }
845
+ else {
846
+ console.log(`[${timestamp}] Zapier SDK: ${message}`);
847
+ }
848
+ };
849
+ }
850
+ function createDebugFetch(options) {
851
+ const { originalFetch, debugLog } = options;
852
+ return async (input, options) => {
853
+ const startTime = Date.now();
854
+ // Convert input to URL string for logging
855
+ const urlString = input instanceof URL
856
+ ? input.toString()
857
+ : typeof input === "string"
858
+ ? input
859
+ : input.url;
860
+ // Log request
861
+ const requestInfo = {
862
+ method: options?.method || "GET",
863
+ url: urlString,
864
+ headers: options?.headers
865
+ ? maskSensitiveHeaders(options.headers)
866
+ : undefined,
867
+ hasBody: !!options?.body,
868
+ };
869
+ debugLog("📤 HTTP Request", requestInfo);
870
+ try {
871
+ const response = await originalFetch(input, options);
872
+ const duration = Date.now() - startTime;
873
+ // Log response
874
+ debugLog("📥 HTTP Response", {
875
+ status: response.status,
876
+ statusText: response.statusText,
877
+ ok: response.ok,
878
+ duration: `${duration}ms`,
879
+ url: urlString,
880
+ });
881
+ return response;
882
+ }
883
+ catch (error) {
884
+ const duration = Date.now() - startTime;
885
+ debugLog("❌ HTTP Error", {
886
+ error: error instanceof Error ? error.message : "Unknown error",
887
+ duration: `${duration}ms`,
888
+ url: urlString,
889
+ });
890
+ throw error;
891
+ }
892
+ };
893
+ }
894
+ function maskSensitiveHeaders(headers) {
895
+ const maskedHeaders = {};
896
+ if (headers instanceof Headers) {
897
+ headers.forEach((value, key) => {
898
+ maskedHeaders[key] =
899
+ key.toLowerCase() === "authorization" ? maskToken(value) : value;
900
+ });
901
+ }
902
+ else if (Array.isArray(headers)) {
903
+ headers.forEach(([key, value]) => {
904
+ maskedHeaders[key] =
905
+ key.toLowerCase() === "authorization" ? maskToken(value) : value;
906
+ });
907
+ }
908
+ else {
909
+ Object.entries(headers).forEach(([key, value]) => {
910
+ maskedHeaders[key] =
911
+ key.toLowerCase() === "authorization" ? maskToken(value) : value;
912
+ });
913
+ }
914
+ return maskedHeaders;
915
+ }
916
+ function maskToken(token) {
917
+ if (token.length <= 8) {
918
+ return "***";
919
+ }
920
+ // Show first 4 and last 4 characters, mask the middle
921
+ const start = token.substring(0, 4);
922
+ const end = token.substring(token.length - 4);
923
+ const middle = "*".repeat(Math.min(token.length - 8, 20)); // Cap mask length
924
+ return `${start}${middle}${end}`;
925
+ }
926
+ async function generateTypes(options, sdk) {
927
+ const { appKey, authId, output = `./types/${appKey}.d.ts` } = options;
928
+ // Parse app identifier (support app@version format)
929
+ const { app, version } = parseAppIdentifier(appKey);
930
+ // Fetch all actions for the app
931
+ const actions = await sdk.actions.list({ appKey: app });
932
+ if (actions.length === 0) {
933
+ const typeDefinitions = generateEmptyTypesFile(app, version);
934
+ if (output) {
935
+ const fs = await Promise.resolve().then(() => __importStar(require("fs")));
936
+ const path = await Promise.resolve().then(() => __importStar(require("path")));
937
+ fs.mkdirSync(path.dirname(output), { recursive: true });
938
+ fs.writeFileSync(output, typeDefinitions, "utf8");
939
+ }
940
+ return typeDefinitions;
941
+ }
942
+ // Fetch input fields for each action
943
+ const actionsWithFields = [];
944
+ if (authId) {
945
+ for (const action of actions) {
946
+ try {
947
+ const fields = await sdk.fields.list({
948
+ app: action.appKey,
949
+ action: action.key,
950
+ type: action.type,
951
+ authId: authId,
952
+ });
953
+ actionsWithFields.push({ ...action, inputFields: fields });
954
+ }
955
+ catch {
956
+ // If we can't get fields for an action, include it without fields
957
+ actionsWithFields.push({ ...action, inputFields: [] });
958
+ }
959
+ }
960
+ }
961
+ else {
962
+ // Convert actions to have empty input fields (will generate generic types)
963
+ actions.forEach((action) => {
964
+ actionsWithFields.push({ ...action, inputFields: [] });
965
+ });
966
+ }
967
+ // Generate TypeScript types
968
+ const typeDefinitions = generateTypeDefinitions(app, actionsWithFields, version);
969
+ // Write to file if output path specified
970
+ if (output) {
971
+ const fs = await Promise.resolve().then(() => __importStar(require("fs")));
972
+ const path = await Promise.resolve().then(() => __importStar(require("path")));
973
+ fs.mkdirSync(path.dirname(output), { recursive: true });
974
+ fs.writeFileSync(output, typeDefinitions, "utf8");
975
+ }
976
+ return typeDefinitions;
977
+ }
978
+ function parseAppIdentifier(identifier) {
979
+ const parts = identifier.split("@");
980
+ return {
981
+ app: parts[0],
982
+ version: parts[1],
983
+ };
984
+ }
985
+ function generateTypeDefinitions(appKey, actions, version) {
986
+ // Handle empty actions
987
+ if (actions.length === 0) {
988
+ return generateEmptyTypesFile(appKey, version);
989
+ }
990
+ // Group actions by type
991
+ const actionsByType = actions.reduce((acc, action) => {
992
+ if (!acc[action.type]) {
993
+ acc[action.type] = [];
994
+ }
995
+ acc[action.type].push(action);
996
+ return acc;
997
+ }, {});
998
+ const appName = capitalize(appKey);
999
+ const versionComment = version
1000
+ ? ` * Generated for ${appKey}@${version}`
1001
+ : ` * Generated for ${appKey}`;
1002
+ let output = `/* eslint-disable @typescript-eslint/naming-convention */
1003
+ /**
1004
+ * Auto-generated TypeScript types for Zapier ${appKey} actions
1005
+ ${versionComment}
1006
+ * Generated on: ${new Date().toISOString()}
1007
+ *
1008
+ * Usage:
1009
+ * import type { ${appName}Actions } from './path/to/this/file'
1010
+ * const sdk: ActionsSdk & { actions: ${appName}Actions } = createActionsSdk(...)
1011
+ */
1012
+
1013
+ import type { ActionExecutionOptions, ActionExecutionResult } from '@zapier/zapier-sdk'
1014
+
1015
+ `;
1016
+ // Generate input types for each action
1017
+ actions.forEach((action) => {
1018
+ if (action.inputFields.length > 0) {
1019
+ const inputTypeName = `${appName}${capitalize(action.type)}${capitalize(sanitizeActionName(action.key))}Inputs`;
1020
+ output += `interface ${inputTypeName} {\n`;
1021
+ action.inputFields.forEach((field) => {
1022
+ const isOptional = !field.required;
1023
+ const fieldType = mapFieldTypeToTypeScript(field);
1024
+ const description = field.helpText
1025
+ ? ` /** ${escapeComment(field.helpText)} */\n`
1026
+ : "";
1027
+ output += `${description} ${sanitizeFieldName(field.key)}${isOptional ? "?" : ""}: ${fieldType}\n`;
1028
+ });
1029
+ output += `}\n\n`;
1030
+ }
1031
+ });
1032
+ // Generate action type interfaces for each action type
1033
+ Object.entries(actionsByType).forEach(([actionType, typeActions]) => {
1034
+ const typeName = `${appName}${capitalize(actionType)}Actions`;
1035
+ output += `interface ${typeName} {\n`;
1036
+ typeActions.forEach((action) => {
1037
+ const actionName = sanitizeActionName(action.key);
1038
+ const description = action.description
1039
+ ? ` /** ${escapeComment(action.description)} */\n`
1040
+ : "";
1041
+ // Generate type-safe action method signature
1042
+ if (action.inputFields.length > 0) {
1043
+ const inputTypeName = `${appName}${capitalize(action.type)}${capitalize(sanitizeActionName(action.key))}Inputs`;
1044
+ output += `${description} ${actionName}: (options: { inputs: ${inputTypeName} } & Omit<ActionExecutionOptions, 'inputs'>) => Promise<ActionExecutionResult>\n`;
1045
+ }
1046
+ else {
1047
+ // No specific input fields available - use generic Record<string, any> for inputs
1048
+ output += `${description} ${actionName}: (options?: { inputs?: Record<string, any> } & ActionExecutionOptions) => Promise<ActionExecutionResult>\n`;
1049
+ }
1050
+ });
1051
+ output += `}\n\n`;
1052
+ });
1053
+ // Generate the main app actions interface
1054
+ output += `export interface ${appName}Actions {\n`;
1055
+ output += ` run: {\n`;
1056
+ output += ` ${appKey}: {\n`;
1057
+ Object.keys(actionsByType).forEach((actionType) => {
1058
+ const typeName = `${appName}${capitalize(actionType)}Actions`;
1059
+ output += ` ${actionType}: ${typeName}\n`;
1060
+ });
1061
+ output += ` }\n`;
1062
+ output += ` }\n`;
1063
+ output += `}\n`;
1064
+ return output;
1065
+ }
1066
+ function generateEmptyTypesFile(appKey, version) {
1067
+ const appName = capitalize(appKey);
1068
+ const versionComment = version
1069
+ ? ` * Generated for ${appKey}@${version}`
1070
+ : ` * Generated for ${appKey}`;
1071
+ return `/* eslint-disable @typescript-eslint/naming-convention */
1072
+ /**
1073
+ * Auto-generated TypeScript types for Zapier ${appKey} actions
1074
+ ${versionComment}
1075
+ * Generated on: ${new Date().toISOString()}
1076
+ *
1077
+ * No actions found for this app.
1078
+ */
1079
+
1080
+ import type { ActionExecutionOptions, ActionExecutionResult } from '@zapier/zapier-sdk'
1081
+
1082
+ export interface ${appName}Actions {
1083
+ run: {
1084
+ ${appKey}: {
1085
+ // No actions available
1086
+ }
1087
+ }
1088
+ }
1089
+ `;
1090
+ }
1091
+ function capitalize(str) {
1092
+ return str.charAt(0).toUpperCase() + str.slice(1).replace(/[-_]/g, "");
1093
+ }
1094
+ function sanitizeActionName(actionKey) {
1095
+ // Ensure the action name is a valid TypeScript identifier
1096
+ return actionKey.replace(/[^a-zA-Z0-9_$]/g, "_");
1097
+ }
1098
+ function sanitizeFieldName(fieldKey) {
1099
+ // Ensure the field name is a valid TypeScript identifier
1100
+ return fieldKey.replace(/[^a-zA-Z0-9_$]/g, "_");
1101
+ }
1102
+ function escapeComment(comment) {
1103
+ // Escape comment text to prevent breaking the JSDoc comment
1104
+ return comment.replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
1105
+ }
1106
+ function mapFieldTypeToTypeScript(field) {
1107
+ // Handle choices (enum-like fields)
1108
+ if (field.choices && field.choices.length > 0) {
1109
+ const choiceValues = field.choices
1110
+ .filter((choice) => choice.value !== undefined &&
1111
+ choice.value !== null &&
1112
+ choice.value !== "")
1113
+ .map((choice) => typeof choice.value === "string" ? `"${choice.value}"` : choice.value);
1114
+ if (choiceValues.length > 0) {
1115
+ return choiceValues.join(" | ");
1116
+ }
1117
+ // If all choices were filtered out, fall through to default type handling
1118
+ }
1119
+ // Map Zapier field types to TypeScript types
1120
+ switch (field.type?.toLowerCase()) {
1121
+ case "string":
1122
+ case "text":
1123
+ case "email":
1124
+ case "url":
1125
+ case "password":
1126
+ return "string";
1127
+ case "integer":
1128
+ case "number":
1129
+ return "number";
1130
+ case "boolean":
1131
+ return "boolean";
1132
+ case "datetime":
1133
+ case "date":
1134
+ return "string"; // ISO date strings
1135
+ case "file":
1136
+ return "string"; // File URL or content
1137
+ case "array":
1138
+ return "any[]";
1139
+ case "object":
1140
+ return "Record<string, any>";
1141
+ default:
1142
+ // Default to string for unknown types, with union for common cases
1143
+ return "string | number | boolean";
1144
+ }
1145
+ }
1146
+ // ============================================================================
1147
+ // Bundle Implementation
1148
+ // ============================================================================
1149
+ async function bundleCode(options) {
1150
+ const { input, output, target = "es2020", cjs = false, minify = false, string: returnString = false, } = options;
1151
+ // Dynamically import esbuild
1152
+ const { buildSync } = await Promise.resolve().then(() => __importStar(require("esbuild")));
1153
+ const fs = await Promise.resolve().then(() => __importStar(require("fs")));
1154
+ const path = await Promise.resolve().then(() => __importStar(require("path")));
1155
+ // Resolve input path
1156
+ const resolvedInput = path.resolve(process.cwd(), input);
1157
+ try {
1158
+ // Bundle with esbuild
1159
+ const result = buildSync({
1160
+ entryPoints: [resolvedInput],
1161
+ bundle: true,
1162
+ platform: "node",
1163
+ target: target,
1164
+ format: cjs ? "cjs" : "esm",
1165
+ minify: minify,
1166
+ write: false,
1167
+ external: [], // Bundle everything
1168
+ banner: {
1169
+ js: "#!/usr/bin/env node",
1170
+ },
1171
+ });
1172
+ if (result.errors.length > 0) {
1173
+ throw new Error(`Bundle failed: ${result.errors.map((e) => e.text).join(", ")}`);
1174
+ }
1175
+ const bundledCode = result.outputFiles?.[0]?.text;
1176
+ if (!bundledCode) {
1177
+ throw new Error("No output generated");
1178
+ }
1179
+ let finalOutput = bundledCode;
1180
+ if (returnString) {
1181
+ // Output as quoted string for node -e using JSON.stringify
1182
+ finalOutput = JSON.stringify(bundledCode);
1183
+ }
1184
+ // Write to file if output path specified
1185
+ if (output) {
1186
+ fs.mkdirSync(path.dirname(output), { recursive: true });
1187
+ fs.writeFileSync(output, finalOutput, "utf8");
1188
+ }
1189
+ return finalOutput;
1190
+ }
1191
+ catch (error) {
1192
+ throw new Error(`Bundle failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1193
+ }
1194
+ }