@zapier/zapier-sdk 0.23.2 → 0.24.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.
@@ -0,0 +1,296 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { fetchPlugin } from "./index";
3
+ import { createSdk } from "../../sdk";
4
+ import { eventEmissionPlugin } from "../eventEmission";
5
+ describe("fetch plugin", () => {
6
+ let mockApiClient;
7
+ let mockFetch;
8
+ beforeEach(() => {
9
+ vi.clearAllMocks();
10
+ mockFetch = vi.fn().mockResolvedValue(new Response('{"success": true}', {
11
+ status: 200,
12
+ headers: { "Content-Type": "application/json" },
13
+ }));
14
+ mockApiClient = {
15
+ fetch: mockFetch,
16
+ };
17
+ });
18
+ const apiPlugin = () => ({
19
+ context: {
20
+ api: mockApiClient,
21
+ },
22
+ });
23
+ function createTestSdk() {
24
+ return createSdk()
25
+ .addPlugin(() => ({
26
+ context: {
27
+ options: {},
28
+ },
29
+ }))
30
+ .addPlugin(apiPlugin)
31
+ .addPlugin(eventEmissionPlugin)
32
+ .addPlugin(fetchPlugin);
33
+ }
34
+ describe("URL transformation", () => {
35
+ it("should transform full URLs to relay path format", async () => {
36
+ const sdk = createTestSdk();
37
+ await sdk.fetch("https://api.github.com/user");
38
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.github.com/user", expect.any(Object));
39
+ });
40
+ it("should preserve query parameters and fragments", async () => {
41
+ const sdk = createTestSdk();
42
+ await sdk.fetch("https://api.example.com/search?q=test&limit=10#section");
43
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/search?q=test&limit=10#section", expect.any(Object));
44
+ });
45
+ it("should handle URLs with ports", async () => {
46
+ const sdk = createTestSdk();
47
+ await sdk.fetch("https://api.example.com:8443/data");
48
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com:8443/data", expect.any(Object));
49
+ });
50
+ it("should handle URL objects", async () => {
51
+ const sdk = createTestSdk();
52
+ await sdk.fetch(new URL("https://api.example.com/data"));
53
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", expect.any(Object));
54
+ });
55
+ });
56
+ describe("headers handling", () => {
57
+ it("should pass through regular headers", async () => {
58
+ const sdk = createTestSdk();
59
+ const headers = {
60
+ "Content-Type": "application/json",
61
+ "X-Custom-Header": "test-value",
62
+ };
63
+ await sdk.fetch("https://api.example.com/data", {
64
+ method: "POST",
65
+ headers,
66
+ });
67
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
68
+ method: "POST",
69
+ body: undefined,
70
+ headers: {
71
+ "Content-Type": "application/json",
72
+ "X-Custom-Header": "test-value",
73
+ },
74
+ authRequired: true,
75
+ });
76
+ });
77
+ it("should add relay-specific headers when provided", async () => {
78
+ const sdk = createTestSdk();
79
+ await sdk.fetch("https://api.example.com/data", {
80
+ method: "POST",
81
+ authenticationId: 123,
82
+ callbackUrl: "https://webhook.example.com/callback",
83
+ authenticationTemplate: '{"token": "{{auth.token}}"}',
84
+ });
85
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
86
+ method: "POST",
87
+ body: undefined,
88
+ headers: {
89
+ "X-Relay-Authentication-Id": "123",
90
+ "X-Relay-Callback-Url": "https://webhook.example.com/callback",
91
+ "X-Authentication-Template": '{"token": "{{auth.token}}"}',
92
+ },
93
+ authRequired: true,
94
+ });
95
+ });
96
+ it("should handle Headers object", async () => {
97
+ const sdk = createTestSdk();
98
+ const headers = new Headers();
99
+ headers.set("Content-Type", "application/json");
100
+ headers.set("Authorization", "Bearer token");
101
+ await sdk.fetch("https://api.example.com/data", { headers });
102
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
103
+ method: "GET",
104
+ body: undefined,
105
+ headers: {
106
+ "content-type": "application/json",
107
+ authorization: "Bearer token",
108
+ },
109
+ authRequired: true,
110
+ });
111
+ });
112
+ it("should handle headers as array of tuples", async () => {
113
+ const sdk = createTestSdk();
114
+ const headers = [
115
+ ["Content-Type", "application/json"],
116
+ ["X-Api-Key", "secret"],
117
+ ];
118
+ await sdk.fetch("https://api.example.com/data", { headers });
119
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
120
+ method: "GET",
121
+ body: undefined,
122
+ headers: {
123
+ "Content-Type": "application/json",
124
+ "X-Api-Key": "secret",
125
+ },
126
+ authRequired: true,
127
+ });
128
+ });
129
+ });
130
+ describe("HTTP methods", () => {
131
+ const methods = [
132
+ "GET",
133
+ "POST",
134
+ "PUT",
135
+ "DELETE",
136
+ "PATCH",
137
+ "HEAD",
138
+ "OPTIONS",
139
+ ];
140
+ methods.forEach((method) => {
141
+ it(`should support ${method} method`, async () => {
142
+ const sdk = createTestSdk();
143
+ await sdk.fetch("https://api.example.com/data", { method });
144
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
145
+ method,
146
+ body: undefined,
147
+ headers: {},
148
+ authRequired: true,
149
+ });
150
+ });
151
+ });
152
+ it("should default to GET method", async () => {
153
+ const sdk = createTestSdk();
154
+ await sdk.fetch("https://api.example.com/data");
155
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
156
+ method: "GET",
157
+ body: undefined,
158
+ headers: {},
159
+ authRequired: true,
160
+ });
161
+ });
162
+ });
163
+ describe("request body", () => {
164
+ it("should pass through request body", async () => {
165
+ const sdk = createTestSdk();
166
+ const body = '{"name": "test", "value": 42}';
167
+ await sdk.fetch("https://api.example.com/data", {
168
+ method: "POST",
169
+ body,
170
+ });
171
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
172
+ method: "POST",
173
+ body,
174
+ headers: {
175
+ "Content-Type": "application/json; charset=utf-8",
176
+ },
177
+ authRequired: true,
178
+ });
179
+ });
180
+ it("should auto-set Content-Type for JSON object bodies", async () => {
181
+ const sdk = createTestSdk();
182
+ const body = '{"key": "value"}';
183
+ await sdk.fetch("https://api.example.com/data", {
184
+ method: "POST",
185
+ body,
186
+ });
187
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
188
+ method: "POST",
189
+ body,
190
+ headers: {
191
+ "Content-Type": "application/json; charset=utf-8",
192
+ },
193
+ authRequired: true,
194
+ });
195
+ });
196
+ it("should auto-set Content-Type for JSON array bodies", async () => {
197
+ const sdk = createTestSdk();
198
+ const body = '[{"id": 1}, {"id": 2}]';
199
+ await sdk.fetch("https://api.example.com/data", {
200
+ method: "POST",
201
+ body,
202
+ });
203
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
204
+ method: "POST",
205
+ body,
206
+ headers: {
207
+ "Content-Type": "application/json; charset=utf-8",
208
+ },
209
+ authRequired: true,
210
+ });
211
+ });
212
+ it("should not override explicit Content-Type header", async () => {
213
+ const sdk = createTestSdk();
214
+ const body = '{"key": "value"}';
215
+ await sdk.fetch("https://api.example.com/data", {
216
+ method: "POST",
217
+ body,
218
+ headers: {
219
+ "Content-Type": "text/plain",
220
+ },
221
+ });
222
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
223
+ method: "POST",
224
+ body,
225
+ headers: {
226
+ "Content-Type": "text/plain",
227
+ },
228
+ authRequired: true,
229
+ });
230
+ });
231
+ it("should not set Content-Type for non-JSON bodies", async () => {
232
+ const sdk = createTestSdk();
233
+ const body = "plain text body";
234
+ await sdk.fetch("https://api.example.com/data", {
235
+ method: "POST",
236
+ body,
237
+ });
238
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
239
+ method: "POST",
240
+ body,
241
+ headers: {},
242
+ authRequired: true,
243
+ });
244
+ });
245
+ });
246
+ describe("authentication", () => {
247
+ it("should set authRequired to true in API call options", async () => {
248
+ const sdk = createTestSdk();
249
+ await sdk.fetch("https://api.example.com/data");
250
+ expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
251
+ authRequired: true,
252
+ }));
253
+ });
254
+ });
255
+ describe("response handling", () => {
256
+ it("should return the response from api.fetch", async () => {
257
+ const mockResponse = new Response('{"result": "success"}', {
258
+ status: 200,
259
+ statusText: "OK",
260
+ headers: { "Content-Type": "application/json" },
261
+ });
262
+ mockFetch.mockResolvedValue(mockResponse);
263
+ const sdk = createTestSdk();
264
+ const response = await sdk.fetch("https://api.example.com/data");
265
+ expect(response).toBe(mockResponse);
266
+ expect(response.status).toBe(200);
267
+ });
268
+ it("should handle API errors", async () => {
269
+ const error = new Error("Network error");
270
+ mockFetch.mockRejectedValue(error);
271
+ const sdk = createTestSdk();
272
+ await expect(sdk.fetch("https://api.example.com/data")).rejects.toThrow("Network error");
273
+ });
274
+ });
275
+ describe("context and metadata", () => {
276
+ it("should provide context with meta information", () => {
277
+ const sdk = createTestSdk();
278
+ const context = sdk.getContext();
279
+ expect(context.meta.fetch).toBeDefined();
280
+ expect(context.meta.fetch.inputParameters).toBeDefined();
281
+ expect(context.meta.fetch.inputParameters).toHaveLength(2);
282
+ });
283
+ it("should include cli and mcp in packages", () => {
284
+ const sdk = createTestSdk();
285
+ const context = sdk.getContext();
286
+ expect(context.meta.fetch.packages).toContain("cli");
287
+ expect(context.meta.fetch.packages).toContain("mcp");
288
+ expect(context.meta.fetch.packages).toContain("sdk");
289
+ });
290
+ it("should be in the http category", () => {
291
+ const sdk = createTestSdk();
292
+ const context = sdk.getContext();
293
+ expect(context.meta.fetch.categories).toContain("http");
294
+ });
295
+ });
296
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,cAAc,2DAEI,CAAC;AAEhC,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;kBA0ByB,CAAC"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,cAAc,2DAIxB,CAAC;AAEJ,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;kBAmCzB,CAAC"}
@@ -3,20 +3,25 @@ import { AuthenticationIdPropertySchema } from "../../types/properties";
3
3
  // Schemas for fetch function parameters
4
4
  export const FetchUrlSchema = z
5
5
  .union([z.string(), z.instanceof(URL)])
6
- .describe("The URL to fetch");
6
+ .describe("The full URL of the API endpoint to call (proxied through Zapier's Relay service)");
7
7
  export const FetchInitSchema = z
8
8
  .object({
9
9
  method: z
10
10
  .enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
11
- .optional(),
12
- headers: z.record(z.string(), z.string()).optional(),
11
+ .optional()
12
+ .describe("HTTP method for the request (defaults to GET)"),
13
+ headers: z
14
+ .record(z.string(), z.string())
15
+ .optional()
16
+ .describe("HTTP headers to include in the request"),
13
17
  body: z
14
18
  .union([
15
19
  z.string(),
16
20
  z.instanceof(FormData),
17
21
  z.instanceof(URLSearchParams),
18
22
  ])
19
- .optional(),
23
+ .optional()
24
+ .describe("Request body — JSON strings are auto-detected and Content-Type is set accordingly"),
20
25
  authenticationId: AuthenticationIdPropertySchema.optional(),
21
26
  callbackUrl: z
22
27
  .string()
@@ -28,4 +33,4 @@ export const FetchInitSchema = z
28
33
  .describe("Optional JSON string authentication template to bypass Notary lookup"),
29
34
  })
30
35
  .optional()
31
- .describe("Fetch options including authentication");
36
+ .describe("Request options including method, headers, body, and authentication");
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/registry/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,6BAA6B;CAAG;AAEjD,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK;QAC/C,SAAS,EAAE,qBAAqB,EAAE,CAAC;QACnC,UAAU,EAAE;YACV,GAAG,EAAE,MAAM,CAAC;YACZ,KAAK,EAAE,MAAM,CAAC;YACd,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,EAAE,CAAC;SACrB,EAAE,CAAC;KACL,CAAC;CACH;AAGD,eAAO,MAAM,cAAc,EAAE,MAAM,CACjC,EAAE,EAAE,wBAAwB;AAC5B,EAAE,EAAE,sBAAsB;AAC1B,sBAAsB,CAiJvB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/registry/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,6BAA6B;CAAG;AAEjD,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK;QAC/C,SAAS,EAAE,qBAAqB,EAAE,CAAC;QACnC,UAAU,EAAE;YACV,GAAG,EAAE,MAAM,CAAC;YACZ,KAAK,EAAE,MAAM,CAAC;YACd,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,EAAE,CAAC;SACrB,EAAE,CAAC;KACL,CAAC;CACH;AAGD,eAAO,MAAM,cAAc,EAAE,MAAM,CACjC,EAAE,EAAE,wBAAwB;AAC5B,EAAE,EAAE,sBAAsB;AAC1B,sBAAsB,CAkJvB,CAAC"}
@@ -47,6 +47,7 @@ export const registryPlugin = ({ sdk, context }) => {
47
47
  const meta = context.meta[key];
48
48
  return {
49
49
  name: key,
50
+ description: meta.description,
50
51
  type: meta.type,
51
52
  itemType: meta.itemType,
52
53
  returnType: meta.returnType,
@@ -1,7 +1,7 @@
1
- import type { Plugin } from "../../types/plugin";
2
- import type { ApiClient } from "../../api";
1
+ import type { Plugin, GetSdkType } from "../../types/plugin";
3
2
  import { RelayRequestSchema, type RelayRequestOptions } from "./schemas";
4
3
  import type { EventEmissionContext } from "../eventEmission";
4
+ import type { FetchPluginProvides } from "../fetch";
5
5
  export interface RequestPluginProvides {
6
6
  request: (options: RelayRequestOptions) => Promise<Response>;
7
7
  context: {
@@ -12,10 +12,11 @@ export interface RequestPluginProvides {
12
12
  };
13
13
  };
14
14
  }
15
- export declare const requestPlugin: Plugin<{}, // no SDK dependencies
16
- // no SDK dependencies
17
- {
18
- api: ApiClient;
19
- } & EventEmissionContext, // requires api in context
15
+ /**
16
+ * @deprecated Use `sdk.fetch(url, init?)` instead. This plugin now delegates to the fetch plugin
17
+ * and is kept only for backward compatibility. It is hidden from the CLI/MCP registries.
18
+ */
19
+ export declare const requestPlugin: Plugin<GetSdkType<FetchPluginProvides>, // requires fetch in SDK
20
+ EventEmissionContext, // requires eventEmission context for telemetry
20
21
  RequestPluginProvides>;
21
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/request/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAoB7D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7D,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,OAAO,EAAE;gBACP,WAAW,EAAE,OAAO,kBAAkB,CAAC;aACxC,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAChC,EAAE,EAAE,sBAAsB;AAC1B,AADI,sBAAsB;AAC1B;IAAE,GAAG,EAAE,SAAS,CAAA;CAAE,GAAG,oBAAoB,EAAE,0BAA0B;AACrE,qBAAqB,CA4FtB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/request/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7D,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,OAAO,EAAE;gBACP,WAAW,EAAE,OAAO,kBAAkB,CAAC;aACxC,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAChC,UAAU,CAAC,mBAAmB,CAAC,EAAE,wBAAwB;AACzD,oBAAoB,EAAE,+CAA+C;AACrE,qBAAqB,CAgDtB,CAAC"}
@@ -1,64 +1,23 @@
1
1
  import { RelayRequestSchema } from "./schemas";
2
2
  import { createFunction } from "../../utils/function-utils";
3
3
  import { createTelemetryCallback } from "../../utils/telemetry-utils";
4
- import { coerceToNumericId } from "../../utils/id-utils";
4
+ import { logDeprecation } from "../../utils/logging";
5
5
  /**
6
- * Transforms full URLs into Relay format: /relay/{domain}/{path}
7
- *
8
- * @param url - The full URL to transform (e.g., "https://api.github.com/user")
9
- * @returns The relay path format (e.g., "/relay/api.github.com/user")
6
+ * @deprecated Use `sdk.fetch(url, init?)` instead. This plugin now delegates to the fetch plugin
7
+ * and is kept only for backward compatibility. It is hidden from the CLI/MCP registries.
10
8
  */
11
- function transformUrlToRelayPath(url) {
12
- const targetUrl = new URL(url);
13
- // Build the Relay path: /relay/{host}{pathname}{search}{hash}
14
- // Use host instead of hostname to include port if present
15
- const relayPath = `/relay/${targetUrl.host}${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`;
16
- return relayPath;
17
- }
18
- export const requestPlugin = ({ context }) => {
9
+ export const requestPlugin = ({ sdk, context }) => {
19
10
  async function request(options) {
20
- const { api } = context;
21
- const { url, method = "GET", body, headers: optionsHeaders, authenticationId, callbackUrl, authenticationTemplate, } = options;
22
- // Transform full URL to relay path format
23
- const relayPath = transformUrlToRelayPath(url);
24
- // Build headers for the request
25
- const headers = {};
26
- // Copy existing headers
27
- if (optionsHeaders) {
28
- const headerEntries = optionsHeaders instanceof Headers
29
- ? Array.from(optionsHeaders.entries())
30
- : Array.isArray(optionsHeaders)
31
- ? optionsHeaders
32
- : Object.entries(optionsHeaders);
33
- for (const [key, value] of headerEntries) {
34
- headers[key] = value;
35
- }
36
- }
37
- // Auto-set Content-Type for JSON bodies if not already specified
38
- const hasContentType = Object.keys(headers).some((k) => k.toLowerCase() === "content-type");
39
- if (body && !hasContentType) {
40
- const bodyStr = typeof body === "string" ? body : JSON.stringify(body);
41
- const trimmed = bodyStr.trimStart();
42
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
43
- headers["Content-Type"] = "application/json; charset=utf-8";
44
- }
45
- }
46
- // Add Relay-specific headers
47
- if (authenticationId) {
48
- headers["X-Relay-Authentication-Id"] = coerceToNumericId("authenticationId", authenticationId).toString();
49
- }
50
- if (callbackUrl) {
51
- headers["X-Relay-Callback-Url"] = callbackUrl;
52
- }
53
- if (authenticationTemplate) {
54
- headers["X-Authentication-Template"] = authenticationTemplate;
55
- }
56
- // Use the API client's fetch method directly - let it handle auth automatically
57
- return await api.fetch(relayPath, {
11
+ logDeprecation("request() is deprecated. Use fetch() instead.");
12
+ const { url, method, body, headers, authenticationId, callbackUrl, authenticationTemplate, } = options;
13
+ return sdk.fetch(url, {
58
14
  method,
59
- body,
60
- headers,
61
- authRequired: true,
15
+ body: body,
16
+ headers: headers,
17
+ authenticationId,
18
+ callbackUrl,
19
+ authenticationTemplate,
20
+ _telemetry: { isNested: true },
62
21
  });
63
22
  }
64
23
  const requestDefinition = createFunction(request, RelayRequestSchema, createTelemetryCallback(context.eventEmission.emitMethodCalled, request.name));
@@ -67,7 +26,8 @@ export const requestPlugin = ({ context }) => {
67
26
  context: {
68
27
  meta: {
69
28
  request: {
70
- categories: ["http"],
29
+ packages: ["cli", "mcp"],
30
+ categories: ["http", "deprecated"],
71
31
  returnType: "Response",
72
32
  inputSchema: RelayRequestSchema,
73
33
  },
@@ -1,8 +1,10 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import { ZapierValidationError } from "../../types/errors";
3
3
  import { requestPlugin } from "./index";
4
+ import { fetchPlugin } from "../fetch";
4
5
  import { createSdk } from "../../sdk";
5
- import { eventEmissionPlugin } from "../eventEmission";
6
+ import { eventEmissionPlugin, } from "../eventEmission";
7
+ import { resetDeprecationWarnings } from "../../utils/logging";
6
8
  describe("request plugin", () => {
7
9
  let mockApiClient;
8
10
  let mockFetch;
@@ -30,6 +32,7 @@ describe("request plugin", () => {
30
32
  }))
31
33
  .addPlugin(apiPlugin)
32
34
  .addPlugin(eventEmissionPlugin)
35
+ .addPlugin(fetchPlugin)
33
36
  .addPlugin(requestPlugin);
34
37
  }
35
38
  describe("schema validation", () => {
@@ -353,5 +356,106 @@ describe("request plugin", () => {
353
356
  expect(context.meta.request).toBeDefined();
354
357
  expect(context.meta.request.inputSchema).toBeDefined();
355
358
  });
359
+ it("should be in both cli and mcp packages for backward compat", () => {
360
+ const sdk = createTestSdk();
361
+ const context = sdk.getContext();
362
+ expect(context.meta.request.packages).toContain("cli");
363
+ expect(context.meta.request.packages).toContain("mcp");
364
+ });
365
+ it("should be marked as deprecated", () => {
366
+ const sdk = createTestSdk();
367
+ const context = sdk.getContext();
368
+ expect(context.meta.request.categories).toContain("deprecated");
369
+ expect(context.meta.request.categories).toContain("http");
370
+ });
371
+ });
372
+ describe("deprecation warning", () => {
373
+ let warnSpy;
374
+ beforeEach(() => {
375
+ resetDeprecationWarnings();
376
+ warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
377
+ });
378
+ afterEach(() => {
379
+ warnSpy.mockRestore();
380
+ });
381
+ it("should log a deprecation warning when called", async () => {
382
+ const sdk = createTestSdk();
383
+ await sdk.request({ url: "https://api.example.com/data" });
384
+ expect(warnSpy).toHaveBeenCalledWith("[zapier-sdk] Deprecation: request() is deprecated. Use fetch() instead.");
385
+ });
386
+ it("should only log the deprecation warning once", async () => {
387
+ const sdk = createTestSdk();
388
+ await sdk.request({ url: "https://api.example.com/data" });
389
+ await sdk.request({ url: "https://api.example.com/other" });
390
+ const deprecationCalls = warnSpy.mock.calls.filter((call) => typeof call[0] === "string" && call[0].includes("Deprecation"));
391
+ expect(deprecationCalls).toHaveLength(1);
392
+ });
393
+ });
394
+ describe("backward compatibility with fetch", () => {
395
+ it("should produce the same api.fetch call as sdk.fetch for a simple GET", async () => {
396
+ const sdk = createTestSdk();
397
+ await sdk.request({ url: "https://api.example.com/data" });
398
+ const requestCall = mockFetch.mock.calls[0];
399
+ mockFetch.mockClear();
400
+ await sdk.fetch("https://api.example.com/data");
401
+ const fetchCall = mockFetch.mock.calls[0];
402
+ expect(requestCall).toEqual(fetchCall);
403
+ });
404
+ it("should produce the same api.fetch call as sdk.fetch for a POST with body and auth", async () => {
405
+ const sdk = createTestSdk();
406
+ const body = '{"key": "value"}';
407
+ await sdk.request({
408
+ url: "https://api.example.com/data",
409
+ method: "POST",
410
+ body,
411
+ headers: { "X-Custom": "header" },
412
+ authenticationId: 456,
413
+ callbackUrl: "https://callback.example.com/hook",
414
+ authenticationTemplate: '{"tok": "{{auth.tok}}"}',
415
+ });
416
+ const requestCall = mockFetch.mock.calls[0];
417
+ mockFetch.mockClear();
418
+ await sdk.fetch("https://api.example.com/data", {
419
+ method: "POST",
420
+ body,
421
+ headers: { "X-Custom": "header" },
422
+ authenticationId: 456,
423
+ callbackUrl: "https://callback.example.com/hook",
424
+ authenticationTemplate: '{"tok": "{{auth.tok}}"}',
425
+ });
426
+ const fetchCall = mockFetch.mock.calls[0];
427
+ expect(requestCall).toEqual(fetchCall);
428
+ });
429
+ it("should propagate errors from fetch through request", async () => {
430
+ const error = new Error("Network error");
431
+ mockFetch.mockRejectedValue(error);
432
+ const sdk = createTestSdk();
433
+ await expect(sdk.request({ url: "https://api.example.com/data" })).rejects.toThrow("Network error");
434
+ });
435
+ it("should not emit double telemetry when request delegates to fetch", async () => {
436
+ const emitSpy = vi.fn();
437
+ const mockEventEmissionPlugin = () => ({
438
+ context: {
439
+ eventEmission: {
440
+ transport: {},
441
+ config: {},
442
+ emit: () => { },
443
+ createBaseEvent: (() => ({})),
444
+ emitMethodCalled: emitSpy,
445
+ },
446
+ },
447
+ });
448
+ const sdk = createSdk()
449
+ .addPlugin(() => ({
450
+ context: { options: {} },
451
+ }))
452
+ .addPlugin(apiPlugin)
453
+ .addPlugin(mockEventEmissionPlugin)
454
+ .addPlugin(fetchPlugin)
455
+ .addPlugin(requestPlugin);
456
+ await sdk.request({ url: "https://api.example.com/data" });
457
+ const methodCalledEvents = emitSpy.mock.calls.map((c) => c[0].method_name);
458
+ expect(methodCalledEvents).toEqual(["request"]);
459
+ });
356
460
  });
357
461
  });
@@ -16,7 +16,6 @@ export declare const RelayRequestSchema: z.ZodObject<{
16
16
  callbackUrl: z.ZodOptional<z.ZodString>;
17
17
  authenticationTemplate: z.ZodOptional<z.ZodString>;
18
18
  headers: z.ZodOptional<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodString>, z.ZodCustom<Headers, Headers>, z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodString], null>>]>>;
19
- relayBaseUrl: z.ZodOptional<z.ZodString>;
20
19
  _telemetry: z.ZodOptional<z.ZodObject<{
21
20
  isNested: z.ZodOptional<z.ZodBoolean>;
22
21
  }, z.core.$strip>>;
@@ -42,7 +41,6 @@ export declare const RelayFetchSchema: z.ZodObject<{
42
41
  callbackUrl: z.ZodOptional<z.ZodString>;
43
42
  authenticationTemplate: z.ZodOptional<z.ZodString>;
44
43
  headers: z.ZodOptional<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodString>, z.ZodCustom<Headers, Headers>, z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodString], null>>]>>;
45
- relayBaseUrl: z.ZodOptional<z.ZodString>;
46
44
  _telemetry: z.ZodOptional<z.ZodObject<{
47
45
  isNested: z.ZodOptional<z.ZodBoolean>;
48
46
  }, z.core.$strip>>;
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/plugins/request/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAK5B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;iBAoC+C,CAAC;AAG/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAGrE,MAAM,MAAM,iBAAiB,GACzB,cAAc,GACd,qBAAqB,GACrB,kBAAkB,CAAC;AAGvB,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC9D;AAGD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;iBAAqB,CAAC;AACnD,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AACpD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC1D"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/plugins/request/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAK5B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;iBAiC+C,CAAC;AAG/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAGrE,MAAM,MAAM,iBAAiB,GACzB,cAAc,GACd,qBAAqB,GACrB,kBAAkB,CAAC;AAGvB,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC9D;AAGD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;iBAAqB,CAAC;AACnD,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AACpD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC1D"}
@@ -31,9 +31,6 @@ export const RelayRequestSchema = z
31
31
  ])
32
32
  .optional()
33
33
  .describe("Request headers"),
34
- })
35
- .extend({
36
- relayBaseUrl: z.string().optional().describe("Base URL for Relay service"),
37
34
  })
38
35
  .merge(TelemetryMarkerSchema)
39
36
  .describe("Make authenticated HTTP requests through Zapier's Relay service");
@@ -1,8 +1,8 @@
1
1
  import { z } from "zod";
2
2
  export declare const ActionItemSchema: z.ZodObject<{
3
3
  key: z.ZodString;
4
- id: z.ZodOptional<z.ZodString>;
5
4
  description: z.ZodString;
5
+ id: z.ZodOptional<z.ZodString>;
6
6
  is_important: z.ZodOptional<z.ZodBoolean>;
7
7
  is_hidden: z.ZodOptional<z.ZodBoolean>;
8
8
  app_key: z.ZodString;