@zapier/zapier-sdk 0.25.0 → 0.25.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @zapier/zapier-sdk
2
2
 
3
+ ## 0.25.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 642713b: Update README with more detail and more attention on fetch.
8
+
9
+ ## 0.25.1
10
+
11
+ ### Patch Changes
12
+
13
+ - aacaf22: Improved `fetch` to automatically set the correct `Content-Type` header based on the request body type. `FormData`, `Blob`, and other binary bodies are now handled correctly instead of being misidentified as JSON.
14
+
3
15
  ## 0.25.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## Table of Contents
4
4
 
5
+ - [Closed Beta](#closed-beta)
6
+ - [Documentation](#documentation)
5
7
  - [Installation](#installation)
6
8
  - [Quick Start](#quick-start)
7
9
  - [Available Functions](#available-functions)
@@ -31,10 +33,26 @@
31
33
  - [HTTP Requests](#http-requests)
32
34
  - [`fetch`](#fetch)
33
35
 
36
+ ## Closed Beta
37
+
38
+ At the time of publishing this package, the Zapier SDK requires an invite for most features. If you want to try it out, let us know at the following page!
39
+
40
+ https://sdk-beta.zapier.app/signup
41
+
42
+ ## Documentation
43
+
44
+ The official documentation will soon be available at:
45
+
46
+ https://docs.zapier.com/sdk
47
+
48
+ While the dust settles, that documentation may be incomplete, and this README may have additional documentation.
49
+
50
+ Agents are sometimes blocked from viewing docs on npm, so you may want to provide them a link to the official docs or copy the docs here into a prompt.
51
+
34
52
  ## Installation
35
53
 
36
54
  ```bash
37
- # If starting a new project:
55
+ # If you're starting a new project:
38
56
  cd your-project-dir
39
57
  npm init -y
40
58
  npx tsc --init
@@ -46,20 +64,52 @@ npm install -D @zapier/zapier-sdk-cli @types/node typescript
46
64
 
47
65
  ## Quick Start
48
66
 
67
+ Assuming you've installed the CLI package into your project (see instructions above), you (or an agent) can start using it right away. This is useful for introspecting actions, authentications, etc. that you want to use in code, but you can also use it to directly use integrations.
68
+
49
69
  ```bash
50
- # Authenticates through your browser, automatically handling token management
70
+ # See all available commands
71
+ npx zapier-sdk --help
72
+
73
+ # Login to Zapier.
51
74
  npx zapier-sdk login
52
75
 
53
- # Generate TypeScript types for actions and fields of any apps you want to use.
54
- npx zapier-sdk add slack google-sheets
76
+ # Search from thousands of supported apps.
77
+ npx zapier-sdk list-apps --search "gmail"
78
+ # The output will show you the valid keys next to the app title like this:
79
+ # 1. Gmail (GoogleMailV2CLIAPI, gmail)
80
+
81
+ # Run any action for the app, using one of the app keys.
82
+ npx zapier-sdk run-action gmail
83
+ # This will ask you for the type of action you want to run.
84
+ # `search` or `write` are typically great for testing.
85
+ # Note that you usually need an authentication (connection) to the app to run
86
+ # the action. If you don't already have one, you can create a new one at:
87
+ # https://zapier.com/app/assets/connections
88
+
89
+ # List authentications for an app.
90
+ npx zapier-sdk list-authentications gmail
91
+ # Or only list the ones you own.
92
+ npx zapier-sdk list-authentications gmail --owner me
93
+ # Or just grab the first one.
94
+ npx zapier-sdk find-first-authentication gmail --owner me
95
+
96
+ # Make any API request to an app using your authentication (connection).
97
+ npx zapier-sdk fetch "https://gmail.googleapis.com/gmail/v1/users/me/labels" --authentication-id 123
98
+ ```
55
99
 
56
- # The names of apps are there slugs if available, otherwise internal keys. If
57
- # you don't know the slug or key, just use `list-apps` search to find it.
58
- zapier-sdk list-apps --search "google sheets"
100
+ The following is optional, but if you want to call our actions from code, it can be helpful to install TypeScript types for those actions. You can also just use `fetch` to make arbitrary API requests. In that case, you don't really need this, and instead you might need types or a good agent that will look up third party API docs.
59
101
 
102
+ ```bash
103
+ # Search for apps that you want to use.
104
+ npx zapier-sdk list-apps --search "slack"
105
+ npx zapier-sdk list-apps --search "google sheets"
60
106
  # The output will show you the valid keys next to the app title like this:
107
+ # 1. Slack (SlackCLIAPI, slack)
61
108
  # 1. Google Sheets (GoogleSheetsV2CLIAPI, google-sheets, google_sheets)
62
109
 
110
+ # Generate TypeScript types for actions and fields of any apps you want to use.
111
+ npx zapier-sdk add slack google-sheets
112
+
63
113
  # By default, types will be generated inside your `src` or `lib` folder if you
64
114
  # have one or otherwise the root folder. Make sure your `tsconfig.json` file
65
115
  # includes the generated type files.
@@ -68,20 +118,20 @@ zapier-sdk list-apps --search "google sheets"
68
118
  npx zapier-sdk add slack google-sheets --types-output ./types
69
119
  ```
70
120
 
121
+ Now let's write some code!
122
+
71
123
  ```typescript
72
124
  import { createZapierSdk } from "@zapier/zapier-sdk";
73
125
 
74
126
  // ######## Initialize Zapier SDK ########
75
- // Option 1: Running `zapier-sdk login` authenticates through your browser,
76
- // automatically handling token management.
127
+ // Option 1: Running `zapier-sdk login` authenticates through your browser and
128
+ // stores a token on your local machine. As long as you have the CLI package
129
+ // installed as a development dependency, the SDK will automatically use it.
77
130
  const zapier = createZapierSdk();
78
131
 
79
- // Option 2: Manually provide a token.
80
- // const zapier = createZapierSdk({
81
- // credentials: "your_zapier_token_here", // or use ZAPIER_CREDENTIALS env var
82
- // });
83
-
84
- // Option 3: Manually provide a client ID and client secret.
132
+ // Option 2: Provide a client ID and client secret.
133
+ // You can get these by running `zapier-sdk create-client-credentials`.
134
+ // This allows you to run the SDK in a server/serverless environment.
85
135
  // const zapier = createZapierSdk({
86
136
  // credentials: {
87
137
  // clientId: "your_client_id_here", // or use ZAPIER_CREDENTIALS_CLIENT_ID env var
@@ -89,6 +139,12 @@ const zapier = createZapierSdk();
89
139
  // },
90
140
  // });
91
141
 
142
+ // Option 3: Provide a valid Zapier token.
143
+ // This is for partner OAuth or internal Zapier use.
144
+ // const zapier = createZapierSdk({
145
+ // credentials: "your_zapier_token_here", // or use ZAPIER_CREDENTIALS env var
146
+ // });
147
+
92
148
  // ######## Access Apps ########
93
149
  // List methods return a promise for a page with {data, nextCursor}.
94
150
  const { data: firstPageApps, nextCursor } = await zapier.listApps();
@@ -165,7 +221,8 @@ console.log({
165
221
  });
166
222
 
167
223
  // Option 2: Access actions via the `apps` proxy
168
- // If you've generated TS types for an app using `zapier-sdk add`, you can access the app's actions like this:
224
+ // If you've generated TS types for an app using `zapier-sdk add`, you can
225
+ // access the app's actions like this:
169
226
 
170
227
  // await zapier.apps.slack.read.channles({});
171
228
  // await zapier.apps.slack.write.send_message({})
@@ -218,6 +275,18 @@ console.log({
218
275
  // For example, either of the following are valid:
219
276
  // zapier.apps["google-sheets"]
220
277
  // zapier.apps.google_sheets
278
+
279
+ // We have tens of thousands of actions across thousands of apps, but you're
280
+ // still not limited to those. You can make any arbitrary API request to an app
281
+ // using the same authentications with `.fetch`! This returns a response just
282
+ // like a normal `fetch` call in JavaScript.
283
+ const emojiResponse = await zapier.fetch("https://slack.com/api/emoji.list", {
284
+ authenticationId: myAuths[0].id,
285
+ });
286
+
287
+ const emojiData = await emojiResponse.json();
288
+
289
+ console.log(emojiData.emoji);
221
290
  ```
222
291
 
223
292
  ## Available Functions
@@ -808,16 +877,16 @@ Make authenticated HTTP requests to any API through Zapier's Relay service. Pass
808
877
 
809
878
  **Parameters:**
810
879
 
811
- | Name | Type | Required | Default | Possible Values | Description |
812
- | -------------------------- | ------------------------ | -------- | ------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------- |
813
- | `url` | `string, custom` | ✅ | — | — | The full URL of the API endpoint to call (proxied through Zapier's Relay service) |
814
- | `init` | `object` | ❌ | — | — | Request options including method, headers, body, and authentication |
815
- | ↳ `method` | `string` | ❌ | — | `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS` | HTTP method for the request (defaults to GET) |
816
- | ↳ `headers` | `object` | ❌ | — | — | HTTP headers to include in the request |
817
- | ↳ `body` | `string, custom, custom` | ❌ | — | — | Request body — JSON strings are auto-detected and Content-Type is set accordingly |
818
- | ↳ `authenticationId` | `string, number` | ❌ | — | — | Authentication ID to use for this action |
819
- | ↳ `callbackUrl` | `string` | ❌ | — | — | URL to send async response to (makes request async) |
820
- | ↳ `authenticationTemplate` | `string` | ❌ | — | — | Optional JSON string authentication template to bypass Notary lookup |
880
+ | Name | Type | Required | Default | Possible Values | Description |
881
+ | -------------------------- | -------------------------------- | -------- | ------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
882
+ | `url` | `string, custom` | ✅ | — | — | The full URL of the API endpoint to call (proxied through Zapier's Relay service) |
883
+ | `init` | `object` | ❌ | — | — | Request options including method, headers, body, and authentication |
884
+ | ↳ `method` | `string` | ❌ | — | `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS` | HTTP method for the request (defaults to GET) |
885
+ | ↳ `headers` | `object` | ❌ | — | — | HTTP headers to include in the request |
886
+ | ↳ `body` | `string, custom, custom, record` | ❌ | — | — | Request body — plain objects and JSON strings are auto-detected and Content-Type is set accordingly |
887
+ | ↳ `authenticationId` | `string, number` | ❌ | — | — | Authentication ID to use for this action |
888
+ | ↳ `callbackUrl` | `string` | ❌ | — | — | URL to send async response to (makes request async) |
889
+ | ↳ `authenticationTemplate` | `string` | ❌ | — | — | Optional JSON string authentication template to bypass Notary lookup |
821
890
 
822
891
  **Returns:** `Promise<Response>`
823
892
 
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AA4gBjB,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,SAW3D,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AAkgBjB,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,SAW3D,CAAC"}
@@ -9,6 +9,7 @@ import { createDebugLogger, createDebugFetch } from "./debug";
9
9
  import { pollUntilComplete } from "./polling";
10
10
  import { resolveAuthToken, invalidateCredentialsToken } from "../auth";
11
11
  import { getZapierBaseUrl } from "../utils/url-utils";
12
+ import { isPlainObject } from "../utils/type-guard-utils";
12
13
  import { ZapierApiError, ZapierAuthenticationError, ZapierValidationError, ZapierNotFoundError, } from "../types/errors";
13
14
  // Configuration for paths
14
15
  const pathConfig = {
@@ -27,7 +28,26 @@ class ZapierApiClient {
27
28
  constructor(options) {
28
29
  this.options = options;
29
30
  this.fetch = async (path, init) => {
30
- return this.plainFetch(path, init);
31
+ if (!path.startsWith("/")) {
32
+ throw new ZapierValidationError(`fetch expects a path starting with '/', got: ${path}`);
33
+ }
34
+ if (init?.body && (isPlainObject(init.body) || Array.isArray(init.body))) {
35
+ init.body = JSON.stringify(init.body);
36
+ }
37
+ const { url, pathConfig } = this.buildUrl(path, init?.searchParams);
38
+ const builtHeaders = await this.buildHeaders(init, pathConfig);
39
+ const inputHeaders = new Headers(init?.headers ?? {});
40
+ const mergedHeaders = new Headers();
41
+ builtHeaders.forEach((value, key) => {
42
+ mergedHeaders.set(key, value);
43
+ });
44
+ inputHeaders.forEach((value, key) => {
45
+ mergedHeaders.set(key, value);
46
+ });
47
+ return await this.options.fetch(url, {
48
+ ...init,
49
+ headers: mergedHeaders,
50
+ });
31
51
  };
32
52
  this.get = async (path, options = {}) => {
33
53
  return this.fetchJson("GET", path, undefined, options);
@@ -43,7 +63,7 @@ class ZapierApiClient {
43
63
  };
44
64
  this.poll = async (path, options = {}) => {
45
65
  return pollUntilComplete({
46
- fetchPoll: () => this.plainFetch(path, {
66
+ fetchPoll: () => this.fetch(path, {
47
67
  method: "GET",
48
68
  searchParams: options.searchParams,
49
69
  authRequired: options.authRequired,
@@ -320,13 +340,13 @@ class ZapierApiClient {
320
340
  const wasMissingAuthToken = options.authRequired &&
321
341
  (await this.getAuthToken({ requiredScopes: options.requiredScopes })) ==
322
342
  null;
323
- const response = await this.plainFetch(path, {
343
+ const response = await this.fetch(path, {
324
344
  ...options,
325
345
  method,
326
346
  body: data != null ? JSON.stringify(data) : undefined,
327
347
  headers,
328
348
  });
329
- // plainFetch already handled all auth and headers
349
+ // fetch already handled all auth and headers
330
350
  const result = await this.handleResponse({
331
351
  response,
332
352
  customErrorHandler: options.customErrorHandler,
@@ -342,29 +362,6 @@ class ZapierApiClient {
342
362
  }
343
363
  return result;
344
364
  }
345
- // Plain fetch method for API paths (must start with /)
346
- async plainFetch(path, fetchOptions) {
347
- if (!path.startsWith("/")) {
348
- throw new ZapierValidationError(`plainFetch expects a path starting with '/', got: ${path}`);
349
- }
350
- if (fetchOptions?.body && typeof fetchOptions.body === "object") {
351
- fetchOptions.body = JSON.stringify(fetchOptions.body);
352
- }
353
- const { url, pathConfig } = this.buildUrl(path, fetchOptions?.searchParams);
354
- const builtHeaders = await this.buildHeaders(fetchOptions, pathConfig);
355
- const inputHeaders = new Headers(fetchOptions?.headers ?? {});
356
- const mergedHeaders = new Headers();
357
- builtHeaders.forEach((value, key) => {
358
- mergedHeaders.set(key, value);
359
- });
360
- inputHeaders.forEach((value, key) => {
361
- mergedHeaders.set(key, value);
362
- });
363
- return await this.options.fetch(url, {
364
- ...fetchOptions,
365
- headers: mergedHeaders,
366
- });
367
- }
368
365
  }
369
366
  export const createZapierApi = (options) => {
370
367
  const { debug = false, fetch: originalFetch = globalThis.fetch } = options;
@@ -228,4 +228,82 @@ describe("ApiClient", () => {
228
228
  expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/a/b/c/api/v0/sdk/relay/some/path", expect.any(Object));
229
229
  });
230
230
  });
231
+ describe("fetch body serialization", () => {
232
+ beforeEach(() => {
233
+ mockResolveAuthToken.mockResolvedValue("test-token");
234
+ });
235
+ it("should JSON.stringify plain object bodies", async () => {
236
+ const client = createZapierApi({
237
+ baseUrl: "http://localhost:3000",
238
+ credentials: "test-token",
239
+ debug: false,
240
+ });
241
+ const body = { channel: "C123", text: "hello" };
242
+ await client.fetch("/relay/api.example.com/data", {
243
+ method: "POST",
244
+ body: body,
245
+ });
246
+ const [, options] = mockFetch.mock.calls[0];
247
+ expect(options.body).toBe(JSON.stringify(body));
248
+ });
249
+ it("should JSON.stringify array bodies", async () => {
250
+ const client = createZapierApi({
251
+ baseUrl: "http://localhost:3000",
252
+ credentials: "test-token",
253
+ debug: false,
254
+ });
255
+ const body = [{ id: 1 }, { id: 2 }];
256
+ await client.fetch("/relay/api.example.com/data", {
257
+ method: "POST",
258
+ body: body,
259
+ });
260
+ const [, options] = mockFetch.mock.calls[0];
261
+ expect(options.body).toBe(JSON.stringify(body));
262
+ });
263
+ it("should not stringify FormData bodies", async () => {
264
+ const client = createZapierApi({
265
+ baseUrl: "http://localhost:3000",
266
+ credentials: "test-token",
267
+ debug: false,
268
+ });
269
+ const body = new FormData();
270
+ body.append("file", "contents");
271
+ await client.fetch("/relay/api.example.com/upload", {
272
+ method: "POST",
273
+ body,
274
+ });
275
+ const [, options] = mockFetch.mock.calls[0];
276
+ expect(options.body).toBe(body);
277
+ });
278
+ it("should not stringify Blob bodies", async () => {
279
+ const client = createZapierApi({
280
+ baseUrl: "http://localhost:3000",
281
+ credentials: "test-token",
282
+ debug: false,
283
+ });
284
+ const body = new Blob(["binary data"], {
285
+ type: "application/octet-stream",
286
+ });
287
+ await client.fetch("/relay/api.example.com/upload", {
288
+ method: "POST",
289
+ body,
290
+ });
291
+ const [, options] = mockFetch.mock.calls[0];
292
+ expect(options.body).toBe(body);
293
+ });
294
+ it("should pass string bodies through unchanged", async () => {
295
+ const client = createZapierApi({
296
+ baseUrl: "http://localhost:3000",
297
+ credentials: "test-token",
298
+ debug: false,
299
+ });
300
+ const body = '{"already": "stringified"}';
301
+ await client.fetch("/relay/api.example.com/data", {
302
+ method: "POST",
303
+ body,
304
+ });
305
+ const [, options] = mockFetch.mock.calls[0];
306
+ expect(options.body).toBe(body);
307
+ });
308
+ });
231
309
  });
package/dist/index.cjs CHANGED
@@ -403,9 +403,10 @@ var FetchInitSchema = zod.z.object({
403
403
  body: zod.z.union([
404
404
  zod.z.string(),
405
405
  zod.z.instanceof(FormData),
406
- zod.z.instanceof(URLSearchParams)
406
+ zod.z.instanceof(URLSearchParams),
407
+ zod.z.record(zod.z.string(), zod.z.unknown())
407
408
  ]).optional().describe(
408
- "Request body \u2014 JSON strings are auto-detected and Content-Type is set accordingly"
409
+ "Request body \u2014 plain objects and JSON strings are auto-detected and Content-Type is set accordingly"
409
410
  ),
410
411
  authenticationId: AuthenticationIdPropertySchema.optional(),
411
412
  callbackUrl: zod.z.string().optional().describe("URL to send async response to (makes request async)"),
@@ -430,6 +431,13 @@ function coerceToNumericId(fieldName, value) {
430
431
  return numericValue;
431
432
  }
432
433
 
434
+ // src/utils/type-guard-utils.ts
435
+ function isPlainObject(value) {
436
+ if (typeof value !== "object" || value === null) return false;
437
+ const proto = Object.getPrototypeOf(value);
438
+ return proto === Object.prototype || proto === null;
439
+ }
440
+
433
441
  // src/plugins/fetch/index.ts
434
442
  function transformUrlToRelayPath(url) {
435
443
  const targetUrl = new URL(url);
@@ -446,6 +454,27 @@ function normalizeHeaders(optionsHeaders) {
446
454
  }
447
455
  return headers;
448
456
  }
457
+ function inferContentType(body) {
458
+ if (body instanceof URLSearchParams) {
459
+ return "application/x-www-form-urlencoded; charset=utf-8";
460
+ }
461
+ if (typeof body === "string") {
462
+ const trimmed = body.trimStart();
463
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
464
+ return void 0;
465
+ }
466
+ try {
467
+ JSON.parse(trimmed);
468
+ return "application/json; charset=utf-8";
469
+ } catch {
470
+ return void 0;
471
+ }
472
+ }
473
+ if (isPlainObject(body) || Array.isArray(body)) {
474
+ return "application/json; charset=utf-8";
475
+ }
476
+ return void 0;
477
+ }
449
478
  var fetchPlugin = ({ context }) => {
450
479
  return {
451
480
  fetch: async function fetch2(url, init) {
@@ -468,10 +497,11 @@ var fetchPlugin = ({ context }) => {
468
497
  (k) => k.toLowerCase() === "content-type"
469
498
  );
470
499
  if (fetchInit.body && !hasContentType) {
471
- const bodyStr = typeof fetchInit.body === "string" ? fetchInit.body : JSON.stringify(fetchInit.body);
472
- const trimmed = bodyStr.trimStart();
473
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
474
- headers["Content-Type"] = "application/json; charset=utf-8";
500
+ const inferred = inferContentType(
501
+ fetchInit.body
502
+ );
503
+ if (inferred) {
504
+ headers["Content-Type"] = inferred;
475
505
  }
476
506
  }
477
507
  if (authenticationId) {
@@ -4188,7 +4218,31 @@ var ZapierApiClient = class {
4188
4218
  constructor(options) {
4189
4219
  this.options = options;
4190
4220
  this.fetch = async (path, init) => {
4191
- return this.plainFetch(path, init);
4221
+ if (!path.startsWith("/")) {
4222
+ throw new ZapierValidationError(
4223
+ `fetch expects a path starting with '/', got: ${path}`
4224
+ );
4225
+ }
4226
+ if (init?.body && (isPlainObject(init.body) || Array.isArray(init.body))) {
4227
+ init.body = JSON.stringify(init.body);
4228
+ }
4229
+ const { url, pathConfig: pathConfig2 } = this.buildUrl(path, init?.searchParams);
4230
+ const builtHeaders = await this.buildHeaders(
4231
+ init,
4232
+ pathConfig2
4233
+ );
4234
+ const inputHeaders = new Headers(init?.headers ?? {});
4235
+ const mergedHeaders = new Headers();
4236
+ builtHeaders.forEach((value, key) => {
4237
+ mergedHeaders.set(key, value);
4238
+ });
4239
+ inputHeaders.forEach((value, key) => {
4240
+ mergedHeaders.set(key, value);
4241
+ });
4242
+ return await this.options.fetch(url, {
4243
+ ...init,
4244
+ headers: mergedHeaders
4245
+ });
4192
4246
  };
4193
4247
  this.get = async (path, options = {}) => {
4194
4248
  return this.fetchJson("GET", path, void 0, options);
@@ -4204,7 +4258,7 @@ var ZapierApiClient = class {
4204
4258
  };
4205
4259
  this.poll = async (path, options = {}) => {
4206
4260
  return pollUntilComplete({
4207
- fetchPoll: () => this.plainFetch(path, {
4261
+ fetchPoll: () => this.fetch(path, {
4208
4262
  method: "GET",
4209
4263
  searchParams: options.searchParams,
4210
4264
  authRequired: options.authRequired
@@ -4440,7 +4494,7 @@ var ZapierApiClient = class {
4440
4494
  headers["Content-Type"] = "application/json";
4441
4495
  }
4442
4496
  const wasMissingAuthToken = options.authRequired && await this.getAuthToken({ requiredScopes: options.requiredScopes }) == null;
4443
- const response = await this.plainFetch(path, {
4497
+ const response = await this.fetch(path, {
4444
4498
  ...options,
4445
4499
  method,
4446
4500
  body: data != null ? JSON.stringify(data) : void 0,
@@ -4462,34 +4516,6 @@ var ZapierApiClient = class {
4462
4516
  }
4463
4517
  return result;
4464
4518
  }
4465
- // Plain fetch method for API paths (must start with /)
4466
- async plainFetch(path, fetchOptions) {
4467
- if (!path.startsWith("/")) {
4468
- throw new ZapierValidationError(
4469
- `plainFetch expects a path starting with '/', got: ${path}`
4470
- );
4471
- }
4472
- if (fetchOptions?.body && typeof fetchOptions.body === "object") {
4473
- fetchOptions.body = JSON.stringify(fetchOptions.body);
4474
- }
4475
- const { url, pathConfig: pathConfig2 } = this.buildUrl(path, fetchOptions?.searchParams);
4476
- const builtHeaders = await this.buildHeaders(
4477
- fetchOptions,
4478
- pathConfig2
4479
- );
4480
- const inputHeaders = new Headers(fetchOptions?.headers ?? {});
4481
- const mergedHeaders = new Headers();
4482
- builtHeaders.forEach((value, key) => {
4483
- mergedHeaders.set(key, value);
4484
- });
4485
- inputHeaders.forEach((value, key) => {
4486
- mergedHeaders.set(key, value);
4487
- });
4488
- return await this.options.fetch(url, {
4489
- ...fetchOptions,
4490
- headers: mergedHeaders
4491
- });
4492
- }
4493
4519
  };
4494
4520
  var createZapierApi = (options) => {
4495
4521
  const { debug = false, fetch: originalFetch = globalThis.fetch } = options;
@@ -5129,7 +5155,7 @@ function getCpuTime() {
5129
5155
 
5130
5156
  // package.json
5131
5157
  var package_default = {
5132
- version: "0.25.0"};
5158
+ version: "0.25.2"};
5133
5159
 
5134
5160
  // src/plugins/eventEmission/builders.ts
5135
5161
  function createBaseEvent(context = {}) {
package/dist/index.mjs CHANGED
@@ -381,9 +381,10 @@ var FetchInitSchema = z.object({
381
381
  body: z.union([
382
382
  z.string(),
383
383
  z.instanceof(FormData),
384
- z.instanceof(URLSearchParams)
384
+ z.instanceof(URLSearchParams),
385
+ z.record(z.string(), z.unknown())
385
386
  ]).optional().describe(
386
- "Request body \u2014 JSON strings are auto-detected and Content-Type is set accordingly"
387
+ "Request body \u2014 plain objects and JSON strings are auto-detected and Content-Type is set accordingly"
387
388
  ),
388
389
  authenticationId: AuthenticationIdPropertySchema.optional(),
389
390
  callbackUrl: z.string().optional().describe("URL to send async response to (makes request async)"),
@@ -408,6 +409,13 @@ function coerceToNumericId(fieldName, value) {
408
409
  return numericValue;
409
410
  }
410
411
 
412
+ // src/utils/type-guard-utils.ts
413
+ function isPlainObject(value) {
414
+ if (typeof value !== "object" || value === null) return false;
415
+ const proto = Object.getPrototypeOf(value);
416
+ return proto === Object.prototype || proto === null;
417
+ }
418
+
411
419
  // src/plugins/fetch/index.ts
412
420
  function transformUrlToRelayPath(url) {
413
421
  const targetUrl = new URL(url);
@@ -424,6 +432,27 @@ function normalizeHeaders(optionsHeaders) {
424
432
  }
425
433
  return headers;
426
434
  }
435
+ function inferContentType(body) {
436
+ if (body instanceof URLSearchParams) {
437
+ return "application/x-www-form-urlencoded; charset=utf-8";
438
+ }
439
+ if (typeof body === "string") {
440
+ const trimmed = body.trimStart();
441
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
442
+ return void 0;
443
+ }
444
+ try {
445
+ JSON.parse(trimmed);
446
+ return "application/json; charset=utf-8";
447
+ } catch {
448
+ return void 0;
449
+ }
450
+ }
451
+ if (isPlainObject(body) || Array.isArray(body)) {
452
+ return "application/json; charset=utf-8";
453
+ }
454
+ return void 0;
455
+ }
427
456
  var fetchPlugin = ({ context }) => {
428
457
  return {
429
458
  fetch: async function fetch2(url, init) {
@@ -446,10 +475,11 @@ var fetchPlugin = ({ context }) => {
446
475
  (k) => k.toLowerCase() === "content-type"
447
476
  );
448
477
  if (fetchInit.body && !hasContentType) {
449
- const bodyStr = typeof fetchInit.body === "string" ? fetchInit.body : JSON.stringify(fetchInit.body);
450
- const trimmed = bodyStr.trimStart();
451
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
452
- headers["Content-Type"] = "application/json; charset=utf-8";
478
+ const inferred = inferContentType(
479
+ fetchInit.body
480
+ );
481
+ if (inferred) {
482
+ headers["Content-Type"] = inferred;
453
483
  }
454
484
  }
455
485
  if (authenticationId) {
@@ -4166,7 +4196,31 @@ var ZapierApiClient = class {
4166
4196
  constructor(options) {
4167
4197
  this.options = options;
4168
4198
  this.fetch = async (path, init) => {
4169
- return this.plainFetch(path, init);
4199
+ if (!path.startsWith("/")) {
4200
+ throw new ZapierValidationError(
4201
+ `fetch expects a path starting with '/', got: ${path}`
4202
+ );
4203
+ }
4204
+ if (init?.body && (isPlainObject(init.body) || Array.isArray(init.body))) {
4205
+ init.body = JSON.stringify(init.body);
4206
+ }
4207
+ const { url, pathConfig: pathConfig2 } = this.buildUrl(path, init?.searchParams);
4208
+ const builtHeaders = await this.buildHeaders(
4209
+ init,
4210
+ pathConfig2
4211
+ );
4212
+ const inputHeaders = new Headers(init?.headers ?? {});
4213
+ const mergedHeaders = new Headers();
4214
+ builtHeaders.forEach((value, key) => {
4215
+ mergedHeaders.set(key, value);
4216
+ });
4217
+ inputHeaders.forEach((value, key) => {
4218
+ mergedHeaders.set(key, value);
4219
+ });
4220
+ return await this.options.fetch(url, {
4221
+ ...init,
4222
+ headers: mergedHeaders
4223
+ });
4170
4224
  };
4171
4225
  this.get = async (path, options = {}) => {
4172
4226
  return this.fetchJson("GET", path, void 0, options);
@@ -4182,7 +4236,7 @@ var ZapierApiClient = class {
4182
4236
  };
4183
4237
  this.poll = async (path, options = {}) => {
4184
4238
  return pollUntilComplete({
4185
- fetchPoll: () => this.plainFetch(path, {
4239
+ fetchPoll: () => this.fetch(path, {
4186
4240
  method: "GET",
4187
4241
  searchParams: options.searchParams,
4188
4242
  authRequired: options.authRequired
@@ -4418,7 +4472,7 @@ var ZapierApiClient = class {
4418
4472
  headers["Content-Type"] = "application/json";
4419
4473
  }
4420
4474
  const wasMissingAuthToken = options.authRequired && await this.getAuthToken({ requiredScopes: options.requiredScopes }) == null;
4421
- const response = await this.plainFetch(path, {
4475
+ const response = await this.fetch(path, {
4422
4476
  ...options,
4423
4477
  method,
4424
4478
  body: data != null ? JSON.stringify(data) : void 0,
@@ -4440,34 +4494,6 @@ var ZapierApiClient = class {
4440
4494
  }
4441
4495
  return result;
4442
4496
  }
4443
- // Plain fetch method for API paths (must start with /)
4444
- async plainFetch(path, fetchOptions) {
4445
- if (!path.startsWith("/")) {
4446
- throw new ZapierValidationError(
4447
- `plainFetch expects a path starting with '/', got: ${path}`
4448
- );
4449
- }
4450
- if (fetchOptions?.body && typeof fetchOptions.body === "object") {
4451
- fetchOptions.body = JSON.stringify(fetchOptions.body);
4452
- }
4453
- const { url, pathConfig: pathConfig2 } = this.buildUrl(path, fetchOptions?.searchParams);
4454
- const builtHeaders = await this.buildHeaders(
4455
- fetchOptions,
4456
- pathConfig2
4457
- );
4458
- const inputHeaders = new Headers(fetchOptions?.headers ?? {});
4459
- const mergedHeaders = new Headers();
4460
- builtHeaders.forEach((value, key) => {
4461
- mergedHeaders.set(key, value);
4462
- });
4463
- inputHeaders.forEach((value, key) => {
4464
- mergedHeaders.set(key, value);
4465
- });
4466
- return await this.options.fetch(url, {
4467
- ...fetchOptions,
4468
- headers: mergedHeaders
4469
- });
4470
- }
4471
4497
  };
4472
4498
  var createZapierApi = (options) => {
4473
4499
  const { debug = false, fetch: originalFetch = globalThis.fetch } = options;
@@ -5107,7 +5133,7 @@ function getCpuTime() {
5107
5133
 
5108
5134
  // package.json
5109
5135
  var package_default = {
5110
- version: "0.25.0"};
5136
+ version: "0.25.2"};
5111
5137
 
5112
5138
  // src/plugins/eventEmission/builders.ts
5113
5139
  function createBaseEvent(context = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAoC7D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CACL,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,IAAI,CAAC,EAAE,WAAW,GAAG;QACnB,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACnC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;QAChC,UAAU,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;KACrC,KACE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,KAAK,EAAE;gBACL,WAAW,EAAE,MAAM,CAAC;gBACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;gBACnB,UAAU,EAAE,MAAM,EAAE,CAAC;gBACrB,UAAU,EAAE,MAAM,CAAC;gBACnB,eAAe,EAAE,KAAK,CAAC;oBAAE,IAAI,EAAE,MAAM,CAAC;oBAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAA;iBAAE,CAAC,CAAC;aAC/D,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAC9B,EAAE,EAAE,sBAAsB;AAC1B,AADI,sBAAsB;AAC1B;IAAE,GAAG,EAAE,SAAS,CAAA;CAAE,GAAG,oBAAoB,EAAE,0CAA0C;AACrF,mBAAmB,CAsHpB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG;IACjD,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AA6D7D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CACL,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,IAAI,CAAC,EAAE,WAAW,GAAG;QACnB,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACnC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;QAChC,UAAU,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;KACrC,KACE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,KAAK,EAAE;gBACL,WAAW,EAAE,MAAM,CAAC;gBACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;gBACnB,UAAU,EAAE,MAAM,EAAE,CAAC;gBACrB,UAAU,EAAE,MAAM,CAAC;gBACnB,eAAe,EAAE,KAAK,CAAC;oBAAE,IAAI,EAAE,MAAM,CAAC;oBAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAA;iBAAE,CAAC,CAAC;aAC/D,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAC9B,EAAE,EAAE,sBAAsB;AAC1B,AADI,sBAAsB;AAC1B;IAAE,GAAG,EAAE,SAAS,CAAA;CAAE,GAAG,oBAAoB,EAAE,0CAA0C;AACrF,mBAAmB,CAmHpB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG;IACjD,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { FetchUrlSchema, FetchInitSchema } from "./schemas";
2
2
  import { coerceToNumericId } from "../../utils/id-utils";
3
+ import { isPlainObject } from "../../utils/type-guard-utils";
3
4
  /**
4
5
  * Transforms full URLs into Relay format: /relay/{domain}/{path}
5
6
  */
@@ -25,6 +26,28 @@ function normalizeHeaders(optionsHeaders) {
25
26
  }
26
27
  return headers;
27
28
  }
29
+ function inferContentType(body) {
30
+ if (body instanceof URLSearchParams) {
31
+ return "application/x-www-form-urlencoded; charset=utf-8";
32
+ }
33
+ if (typeof body === "string") {
34
+ const trimmed = body.trimStart();
35
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
36
+ return undefined;
37
+ }
38
+ try {
39
+ JSON.parse(trimmed);
40
+ return "application/json; charset=utf-8";
41
+ }
42
+ catch {
43
+ return undefined;
44
+ }
45
+ }
46
+ if (isPlainObject(body) || Array.isArray(body)) {
47
+ return "application/json; charset=utf-8";
48
+ }
49
+ return undefined;
50
+ }
28
51
  /**
29
52
  * Fetch plugin — the primary way to make authenticated HTTP requests through Zapier's Relay service.
30
53
  * Mirrors the native fetch(url, init?) signature with additional Zapier-specific options.
@@ -39,15 +62,11 @@ export const fetchPlugin = ({ context }) => {
39
62
  const { authenticationId, callbackUrl, authenticationTemplate, _telemetry, ...fetchInit } = init || {};
40
63
  const relayPath = transformUrlToRelayPath(url);
41
64
  const headers = normalizeHeaders(fetchInit.headers);
42
- // Auto-set Content-Type for JSON bodies if not already specified
43
65
  const hasContentType = Object.keys(headers).some((k) => k.toLowerCase() === "content-type");
44
66
  if (fetchInit.body && !hasContentType) {
45
- const bodyStr = typeof fetchInit.body === "string"
46
- ? fetchInit.body
47
- : JSON.stringify(fetchInit.body);
48
- const trimmed = bodyStr.trimStart();
49
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
50
- headers["Content-Type"] = "application/json; charset=utf-8";
67
+ const inferred = inferContentType(fetchInit.body);
68
+ if (inferred) {
69
+ headers["Content-Type"] = inferred;
51
70
  }
52
71
  }
53
72
  if (authenticationId) {
@@ -242,6 +242,99 @@ describe("fetch plugin", () => {
242
242
  authRequired: true,
243
243
  });
244
244
  });
245
+ it("should not set Content-Type for FormData bodies", async () => {
246
+ const sdk = createTestSdk();
247
+ const body = new FormData();
248
+ body.append("file", "contents");
249
+ await sdk.fetch("https://api.example.com/upload", {
250
+ method: "POST",
251
+ body,
252
+ });
253
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/upload", {
254
+ method: "POST",
255
+ body,
256
+ headers: {},
257
+ authRequired: true,
258
+ });
259
+ });
260
+ it("should set Content-Type for URLSearchParams bodies", async () => {
261
+ const sdk = createTestSdk();
262
+ const body = new URLSearchParams({ key: "value", foo: "bar" });
263
+ await sdk.fetch("https://api.example.com/form", {
264
+ method: "POST",
265
+ body,
266
+ });
267
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/form", {
268
+ method: "POST",
269
+ body,
270
+ headers: {
271
+ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
272
+ },
273
+ authRequired: true,
274
+ });
275
+ });
276
+ it("should not set Content-Type for invalid JSON-like strings", async () => {
277
+ const sdk = createTestSdk();
278
+ const body = "{ this is not json";
279
+ await sdk.fetch("https://api.example.com/data", {
280
+ method: "POST",
281
+ body,
282
+ });
283
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
284
+ method: "POST",
285
+ body,
286
+ headers: {},
287
+ authRequired: true,
288
+ });
289
+ });
290
+ it("should auto-set Content-Type for plain object bodies", async () => {
291
+ const sdk = createTestSdk();
292
+ const body = { channel: "C0123456789", text: "Hello, world!" };
293
+ await sdk.fetch("https://api.example.com/data", {
294
+ method: "POST",
295
+ body: body,
296
+ });
297
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
298
+ method: "POST",
299
+ body,
300
+ headers: {
301
+ "Content-Type": "application/json; charset=utf-8",
302
+ },
303
+ authRequired: true,
304
+ });
305
+ });
306
+ it("should auto-set Content-Type for plain array bodies", async () => {
307
+ const sdk = createTestSdk();
308
+ const body = [{ id: 1 }, { id: 2 }];
309
+ await sdk.fetch("https://api.example.com/data", {
310
+ method: "POST",
311
+ body: body,
312
+ });
313
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
314
+ method: "POST",
315
+ body,
316
+ headers: {
317
+ "Content-Type": "application/json; charset=utf-8",
318
+ },
319
+ authRequired: true,
320
+ });
321
+ });
322
+ it("should not set Content-Type for Blob bodies", async () => {
323
+ const sdk = createTestSdk();
324
+ const body = new Blob(["binary data"], {
325
+ type: "application/octet-stream",
326
+ });
327
+ await sdk.fetch("https://api.example.com/upload", {
328
+ method: "POST",
329
+ body,
330
+ });
331
+ expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/upload", {
332
+ method: "POST",
333
+ body,
334
+ headers: {},
335
+ authRequired: true,
336
+ });
337
+ });
245
338
  });
246
339
  describe("authentication", () => {
247
340
  it("should set authRequired to true in API call options", async () => {
@@ -11,7 +11,7 @@ export declare const FetchInitSchema: z.ZodOptional<z.ZodObject<{
11
11
  OPTIONS: "OPTIONS";
12
12
  }>>;
13
13
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
14
- body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<FormData, FormData>, z.ZodCustom<URLSearchParams, URLSearchParams>]>>;
14
+ body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<FormData, FormData>, z.ZodCustom<URLSearchParams, URLSearchParams>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
15
15
  authenticationId: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
16
16
  callbackUrl: z.ZodOptional<z.ZodString>;
17
17
  authenticationTemplate: z.ZodOptional<z.ZodString>;
@@ -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,2DAIxB,CAAC;AAEJ,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;kBAmCzB,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;;;;;;;;;;;;;;;kBAoCzB,CAAC"}
@@ -19,9 +19,10 @@ export const FetchInitSchema = z
19
19
  z.string(),
20
20
  z.instanceof(FormData),
21
21
  z.instanceof(URLSearchParams),
22
+ z.record(z.string(), z.unknown()),
22
23
  ])
23
24
  .optional()
24
- .describe("Request body — JSON strings are auto-detected and Content-Type is set accordingly"),
25
+ .describe("Request body — plain objects and JSON strings are auto-detected and Content-Type is set accordingly"),
25
26
  authenticationId: AuthenticationIdPropertySchema.optional(),
26
27
  callbackUrl: z
27
28
  .string()
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Returns true only for plain objects created via `{}`, `new Object()`, or
3
+ * `Object.create(null)`. Rejects all class instances (FormData, Blob, etc.)
4
+ * without needing to enumerate them.
5
+ */
6
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
7
+ //# sourceMappingURL=type-guard-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-guard-utils.d.ts","sourceRoot":"","sources":["../../src/utils/type-guard-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIlC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Returns true only for plain objects created via `{}`, `new Object()`, or
3
+ * `Object.create(null)`. Rejects all class instances (FormData, Blob, etc.)
4
+ * without needing to enumerate them.
5
+ */
6
+ export function isPlainObject(value) {
7
+ if (typeof value !== "object" || value === null)
8
+ return false;
9
+ const proto = Object.getPrototypeOf(value);
10
+ return proto === Object.prototype || proto === null;
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk",
3
- "version": "0.25.0",
3
+ "version": "0.25.2",
4
4
  "description": "Complete Zapier SDK - combines all Zapier SDK packages",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",