@zapier/zapier-sdk 0.15.3 → 0.15.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/api/auth.d.ts +10 -0
  3. package/dist/api/auth.d.ts.map +1 -1
  4. package/dist/api/auth.js +45 -0
  5. package/dist/api/auth.test.d.ts +2 -0
  6. package/dist/api/auth.test.d.ts.map +1 -0
  7. package/dist/api/auth.test.js +220 -0
  8. package/dist/api/client.d.ts.map +1 -1
  9. package/dist/api/client.js +18 -32
  10. package/dist/api/client.methods.test.d.ts +2 -0
  11. package/dist/api/client.methods.test.d.ts.map +1 -0
  12. package/dist/api/client.methods.test.js +158 -0
  13. package/dist/api/client.test.js +27 -11
  14. package/dist/api/router.d.ts +16 -0
  15. package/dist/api/router.d.ts.map +1 -0
  16. package/dist/api/router.js +37 -0
  17. package/dist/api/router.test.d.ts +2 -0
  18. package/dist/api/router.test.d.ts.map +1 -0
  19. package/dist/api/router.test.js +109 -0
  20. package/dist/api/schemas.d.ts +38 -38
  21. package/dist/auth.d.ts +15 -0
  22. package/dist/auth.d.ts.map +1 -1
  23. package/dist/auth.js +25 -0
  24. package/dist/index.cjs +350 -87
  25. package/dist/index.d.mts +430 -269
  26. package/dist/index.mjs +350 -88
  27. package/dist/plugins/eventEmission/index.d.ts +1 -1
  28. package/dist/plugins/eventEmission/index.d.ts.map +1 -1
  29. package/dist/plugins/eventEmission/index.js +94 -22
  30. package/dist/plugins/eventEmission/index.test.js +340 -2
  31. package/dist/plugins/getAuthentication/index.d.ts +2 -5
  32. package/dist/plugins/getAuthentication/index.d.ts.map +1 -1
  33. package/dist/plugins/getAuthentication/index.js +3 -24
  34. package/dist/plugins/getAuthentication/index.test.js +32 -144
  35. package/dist/plugins/getAuthentication/schemas.d.ts +4 -13
  36. package/dist/plugins/getAuthentication/schemas.d.ts.map +1 -1
  37. package/dist/plugins/getAuthentication/schemas.js +1 -11
  38. package/dist/schemas/Action.d.ts +1 -1
  39. package/dist/schemas/Auth.d.ts +6 -6
  40. package/dist/sdk.d.ts +1 -1
  41. package/dist/temporary-internal-core/handlers/getAuthentication.d.ts +94 -0
  42. package/dist/temporary-internal-core/handlers/getAuthentication.d.ts.map +1 -0
  43. package/dist/temporary-internal-core/handlers/getAuthentication.js +68 -0
  44. package/dist/temporary-internal-core/handlers/getAuthentication.test.d.ts +2 -0
  45. package/dist/temporary-internal-core/handlers/getAuthentication.test.d.ts.map +1 -0
  46. package/dist/temporary-internal-core/handlers/getAuthentication.test.js +248 -0
  47. package/dist/temporary-internal-core/handlers/listApps.js +1 -1
  48. package/dist/temporary-internal-core/index.d.ts +2 -0
  49. package/dist/temporary-internal-core/index.d.ts.map +1 -1
  50. package/dist/temporary-internal-core/index.js +2 -0
  51. package/dist/temporary-internal-core/schemas/authentications/index.d.ts +454 -0
  52. package/dist/temporary-internal-core/schemas/authentications/index.d.ts.map +1 -0
  53. package/dist/temporary-internal-core/schemas/authentications/index.js +96 -0
  54. package/dist/temporary-internal-core/schemas/errors/index.d.ts +139 -0
  55. package/dist/temporary-internal-core/schemas/errors/index.d.ts.map +1 -0
  56. package/dist/temporary-internal-core/schemas/errors/index.js +129 -0
  57. package/dist/temporary-internal-core/utils/app-locators.d.ts +0 -20
  58. package/dist/temporary-internal-core/utils/app-locators.d.ts.map +1 -1
  59. package/dist/temporary-internal-core/utils/app-locators.js +1 -45
  60. package/dist/temporary-internal-core/utils/string-utils.d.ts +28 -0
  61. package/dist/temporary-internal-core/utils/string-utils.d.ts.map +1 -0
  62. package/dist/temporary-internal-core/utils/string-utils.js +52 -0
  63. package/dist/temporary-internal-core/utils/transformations.d.ts +14 -0
  64. package/dist/temporary-internal-core/utils/transformations.d.ts.map +1 -1
  65. package/dist/temporary-internal-core/utils/transformations.js +37 -1
  66. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @zapier/zapier-sdk
2
2
 
3
+ ## 0.15.8
4
+
5
+ ### Patch Changes
6
+
7
+ - 371315e: Extract main logic/transformations of getAuthentication into temporary-internal-core to prepare for full migration
8
+
9
+ ## 0.15.7
10
+
11
+ ### Patch Changes
12
+
13
+ - 9f3695b: Make sure path of base URL is used when overriding base URL.
14
+
15
+ ## 0.15.6
16
+
17
+ ### Patch Changes
18
+
19
+ - 6a0e0db: Fix telemetry user context to use token from SDK options and Deduplicate JWT validation logic
20
+
21
+ ## 0.15.5
22
+
23
+ ### Patch Changes
24
+
25
+ - 712c02b: Refactored temporary route override pattern
26
+
27
+ ## 0.15.4
28
+
29
+ ### Patch Changes
30
+
31
+ - f7e552e: Add synchronous customuser_id and account_id retrieval to event emission plugin
32
+
3
33
  ## 0.15.3
4
34
 
5
35
  ### Patch Changes
@@ -6,4 +6,14 @@
6
6
  */
7
7
  export declare function isJwt(token: string): boolean;
8
8
  export declare function getAuthorizationHeader(token: string): string;
9
+ /**
10
+ * Extract user IDs from JWT token
11
+ * Decodes the JWT payload and extracts customuser_id and account_id
12
+ * Handles nested JWTs for service tokens
13
+ * Returns null values on any failure (silent-by-design)
14
+ */
15
+ export declare function extractUserIdsFromJwt(token: string): {
16
+ customuser_id: number | null;
17
+ account_id: number | null;
18
+ };
9
19
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAW5C;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO5D"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAW5C;AAaD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO5D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG;IACpD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAwCA"}
package/dist/api/auth.js CHANGED
@@ -15,6 +15,16 @@ export function isJwt(token) {
15
15
  const base64UrlPattern = /^[A-Za-z0-9_-]+$/;
16
16
  return parts.every((part) => part.length > 0 && base64UrlPattern.test(part));
17
17
  }
18
+ /**
19
+ * Parse a JWT token into its three parts
20
+ * Returns the parts array if valid, null otherwise
21
+ */
22
+ function parseJwt(token) {
23
+ if (!isJwt(token)) {
24
+ return null;
25
+ }
26
+ return token.split(".");
27
+ }
18
28
  export function getAuthorizationHeader(token) {
19
29
  // Check if token is a JWT (has 3 parts separated by dots)
20
30
  if (isJwt(token)) {
@@ -23,3 +33,38 @@ export function getAuthorizationHeader(token) {
23
33
  // Default to Bearer for other token types
24
34
  return `Bearer ${token}`;
25
35
  }
36
+ /**
37
+ * Extract user IDs from JWT token
38
+ * Decodes the JWT payload and extracts customuser_id and account_id
39
+ * Handles nested JWTs for service tokens
40
+ * Returns null values on any failure (silent-by-design)
41
+ */
42
+ export function extractUserIdsFromJwt(token) {
43
+ const parts = parseJwt(token);
44
+ if (!parts) {
45
+ return { customuser_id: null, account_id: null };
46
+ }
47
+ try {
48
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
49
+ let actualPayload = payload;
50
+ if (payload.sub_type === "service" && payload.njwt) {
51
+ const nestedParts = payload.njwt.split(".");
52
+ if (nestedParts.length === 3) {
53
+ actualPayload = JSON.parse(Buffer.from(nestedParts[1], "base64url").toString("utf-8"));
54
+ }
55
+ }
56
+ const accountId = actualPayload["zap:acc"] != null
57
+ ? parseInt(String(actualPayload["zap:acc"]), 10)
58
+ : null;
59
+ const customUserId = actualPayload.sub_type === "customuser" && actualPayload.sub != null
60
+ ? parseInt(String(actualPayload.sub), 10)
61
+ : null;
62
+ return {
63
+ customuser_id: customUserId !== null && !isNaN(customUserId) ? customUserId : null,
64
+ account_id: accountId !== null && !isNaN(accountId) ? accountId : null,
65
+ };
66
+ }
67
+ catch {
68
+ return { customuser_id: null, account_id: null };
69
+ }
70
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../src/api/auth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,220 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { isJwt, getAuthorizationHeader, extractUserIdsFromJwt } from "./auth";
3
+ describe("api/auth", () => {
4
+ describe("isJwt", () => {
5
+ it("should return true for valid JWT tokens", () => {
6
+ const validJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
7
+ expect(isJwt(validJwt)).toBe(true);
8
+ });
9
+ it("should return true for JWT with minimal valid parts", () => {
10
+ const minimalJwt = "a.b.c";
11
+ expect(isJwt(minimalJwt)).toBe(true);
12
+ });
13
+ it("should return false for tokens with fewer than 3 parts", () => {
14
+ expect(isJwt("header.payload")).toBe(false);
15
+ expect(isJwt("onlyonepart")).toBe(false);
16
+ expect(isJwt("")).toBe(false);
17
+ });
18
+ it("should return false for tokens with more than 3 parts", () => {
19
+ expect(isJwt("part1.part2.part3.part4")).toBe(false);
20
+ });
21
+ it("should return false for tokens with empty parts", () => {
22
+ expect(isJwt("..")).toBe(false);
23
+ expect(isJwt(".payload.signature")).toBe(false);
24
+ expect(isJwt("header..signature")).toBe(false);
25
+ expect(isJwt("header.payload.")).toBe(false);
26
+ });
27
+ it("should return false for tokens with invalid base64url characters", () => {
28
+ expect(isJwt("header+plus.payload.signature")).toBe(false);
29
+ expect(isJwt("header.payload/slash.signature")).toBe(false);
30
+ expect(isJwt("header.payload=padding.signature")).toBe(false);
31
+ expect(isJwt("header.payload with space.signature")).toBe(false);
32
+ });
33
+ it("should accept valid base64url characters including dashes and underscores", () => {
34
+ const validBase64Url = "abc-123_XYZ.def-456_UVW.ghi-789_RST";
35
+ expect(isJwt(validBase64Url)).toBe(true);
36
+ });
37
+ });
38
+ describe("getAuthorizationHeader", () => {
39
+ it("should return JWT prefix for valid JWT tokens", () => {
40
+ const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U";
41
+ expect(getAuthorizationHeader(jwt)).toBe(`JWT ${jwt}`);
42
+ });
43
+ it("should return Bearer prefix for non-JWT tokens", () => {
44
+ const apiKey = "sk_test_1234567890abcdef";
45
+ expect(getAuthorizationHeader(apiKey)).toBe(`Bearer ${apiKey}`);
46
+ });
47
+ it("should return Bearer prefix for tokens with wrong number of parts", () => {
48
+ expect(getAuthorizationHeader("header.payload")).toBe("Bearer header.payload");
49
+ });
50
+ it("should return Bearer prefix for empty string", () => {
51
+ expect(getAuthorizationHeader("")).toBe("Bearer ");
52
+ });
53
+ });
54
+ describe("extractUserIdsFromJwt", () => {
55
+ function createJwt(payload) {
56
+ const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
57
+ const payloadEncoded = Buffer.from(JSON.stringify(payload)).toString("base64url");
58
+ const signature = "fake_signature_for_testing";
59
+ return `${header}.${payloadEncoded}.${signature}`;
60
+ }
61
+ it("should return null values for invalid JWT tokens", () => {
62
+ expect(extractUserIdsFromJwt("not-a-jwt")).toEqual({
63
+ customuser_id: null,
64
+ account_id: null,
65
+ });
66
+ });
67
+ it("should return null values for JWT with invalid payload", () => {
68
+ const invalidJwt = "header.not-valid-base64!.signature";
69
+ expect(extractUserIdsFromJwt(invalidJwt)).toEqual({
70
+ customuser_id: null,
71
+ account_id: null,
72
+ });
73
+ });
74
+ it("should extract customuser_id and account_id from regular JWT", () => {
75
+ const payload = {
76
+ sub: "12345",
77
+ sub_type: "customuser",
78
+ "zap:acc": "67890",
79
+ };
80
+ const jwt = createJwt(payload);
81
+ expect(extractUserIdsFromJwt(jwt)).toEqual({
82
+ customuser_id: 12345,
83
+ account_id: 67890,
84
+ });
85
+ });
86
+ it("should handle numeric sub and zap:acc values", () => {
87
+ const payload = {
88
+ sub: 99999,
89
+ sub_type: "customuser",
90
+ "zap:acc": 11111,
91
+ };
92
+ const jwt = createJwt(payload);
93
+ expect(extractUserIdsFromJwt(jwt)).toEqual({
94
+ customuser_id: 99999,
95
+ account_id: 11111,
96
+ });
97
+ });
98
+ it("should return null customuser_id when sub_type is not customuser", () => {
99
+ const payload = {
100
+ sub: "12345",
101
+ sub_type: "other",
102
+ "zap:acc": "67890",
103
+ };
104
+ const jwt = createJwt(payload);
105
+ expect(extractUserIdsFromJwt(jwt)).toEqual({
106
+ customuser_id: null,
107
+ account_id: 67890,
108
+ });
109
+ });
110
+ it("should return null customuser_id when sub is missing", () => {
111
+ const payload = {
112
+ sub_type: "customuser",
113
+ "zap:acc": "67890",
114
+ };
115
+ const jwt = createJwt(payload);
116
+ expect(extractUserIdsFromJwt(jwt)).toEqual({
117
+ customuser_id: null,
118
+ account_id: 67890,
119
+ });
120
+ });
121
+ it("should return null account_id when zap:acc is missing", () => {
122
+ const payload = {
123
+ sub: "12345",
124
+ sub_type: "customuser",
125
+ };
126
+ const jwt = createJwt(payload);
127
+ expect(extractUserIdsFromJwt(jwt)).toEqual({
128
+ customuser_id: 12345,
129
+ account_id: null,
130
+ });
131
+ });
132
+ it("should return null values when both fields are missing", () => {
133
+ const payload = {
134
+ other_field: "value",
135
+ };
136
+ const jwt = createJwt(payload);
137
+ expect(extractUserIdsFromJwt(jwt)).toEqual({
138
+ customuser_id: null,
139
+ account_id: null,
140
+ });
141
+ });
142
+ it("should handle nested JWT for service tokens", () => {
143
+ const nestedPayload = {
144
+ sub: "12345",
145
+ sub_type: "customuser",
146
+ "zap:acc": "67890",
147
+ };
148
+ const nestedJwt = createJwt(nestedPayload);
149
+ const servicePayload = {
150
+ sub_type: "service",
151
+ njwt: nestedJwt,
152
+ };
153
+ const serviceJwt = createJwt(servicePayload);
154
+ expect(extractUserIdsFromJwt(serviceJwt)).toEqual({
155
+ customuser_id: 12345,
156
+ account_id: 67890,
157
+ });
158
+ });
159
+ it("should fall back to outer payload when nested JWT is invalid", () => {
160
+ const servicePayload = {
161
+ sub_type: "service",
162
+ njwt: "invalid.jwt",
163
+ "zap:acc": "99999",
164
+ };
165
+ const serviceJwt = createJwt(servicePayload);
166
+ expect(extractUserIdsFromJwt(serviceJwt)).toEqual({
167
+ customuser_id: null,
168
+ account_id: 99999,
169
+ });
170
+ });
171
+ it("should use outer payload when sub_type is service but njwt is missing", () => {
172
+ const servicePayload = {
173
+ sub: "12345",
174
+ sub_type: "service",
175
+ "zap:acc": "67890",
176
+ };
177
+ const serviceJwt = createJwt(servicePayload);
178
+ expect(extractUserIdsFromJwt(serviceJwt)).toEqual({
179
+ customuser_id: null,
180
+ account_id: 67890,
181
+ });
182
+ });
183
+ it("should return null for non-numeric sub values", () => {
184
+ const payload = {
185
+ sub: "not-a-number",
186
+ sub_type: "customuser",
187
+ "zap:acc": "67890",
188
+ };
189
+ const jwt = createJwt(payload);
190
+ expect(extractUserIdsFromJwt(jwt)).toEqual({
191
+ customuser_id: null,
192
+ account_id: 67890,
193
+ });
194
+ });
195
+ it("should return null for non-numeric zap:acc values", () => {
196
+ const payload = {
197
+ sub: "12345",
198
+ sub_type: "customuser",
199
+ "zap:acc": "not-a-number",
200
+ };
201
+ const jwt = createJwt(payload);
202
+ expect(extractUserIdsFromJwt(jwt)).toEqual({
203
+ customuser_id: 12345,
204
+ account_id: null,
205
+ });
206
+ });
207
+ it("should return null for values that parse to NaN", () => {
208
+ const payload = {
209
+ sub: "abc",
210
+ sub_type: "customuser",
211
+ "zap:acc": "xyz",
212
+ };
213
+ const jwt = createJwt(payload);
214
+ expect(extractUserIdsFromJwt(jwt)).toEqual({
215
+ customuser_id: null,
216
+ account_id: null,
217
+ });
218
+ });
219
+ });
220
+ });
@@ -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;AA2hBjB,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;AAggBjB,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,SAW3D,CAAC"}
@@ -7,10 +7,10 @@
7
7
  import { getAuthorizationHeader } from "./auth";
8
8
  import { createDebugLogger, createDebugFetch } from "./debug";
9
9
  import { pollUntilComplete } from "./polling";
10
- import { getTokenFromEnvOrConfig } from "../auth";
10
+ import { resolveAuthToken } from "../auth";
11
11
  import { getZapierBaseUrl } from "../utils/url-utils";
12
12
  import { ZapierApiError, ZapierAuthenticationError, ZapierValidationError, ZapierNotFoundError, } from "../types/errors";
13
- import { handleListApps } from "../temporary-internal-core";
13
+ import { matchRoute } from "./router";
14
14
  // Configuration for paths
15
15
  const pathConfig = {
16
16
  // e.g. /relay -> https://sdkapi.zapier.com/api/v0/sdk/relay/...
@@ -23,9 +23,6 @@ const pathConfig = {
23
23
  authHeader: "Authorization",
24
24
  pathPrefix: "/api/v0/sdk/zapier",
25
25
  },
26
- "/api/v0/apps": {
27
- handlerOverride: handleListApps,
28
- },
29
26
  };
30
27
  class ZapierApiClient {
31
28
  constructor(options) {
@@ -71,16 +68,9 @@ class ZapierApiClient {
71
68
  }
72
69
  // Helper to get a token from the different places it could be gotten
73
70
  async getAuthToken() {
74
- if (this.options.token) {
75
- return this.options.token;
76
- }
77
- if (this.options.getToken) {
78
- const token = await this.options.getToken();
79
- if (token) {
80
- return token;
81
- }
82
- }
83
- return getTokenFromEnvOrConfig({
71
+ return resolveAuthToken({
72
+ token: this.options.token,
73
+ getToken: this.options.getToken,
84
74
  onEvent: this.options.onEvent,
85
75
  fetch: this.options.fetch,
86
76
  baseUrl: this.options.baseUrl,
@@ -200,16 +190,6 @@ class ZapierApiClient {
200
190
  }
201
191
  return undefined;
202
192
  }
203
- // Helper to check if a path config has a handler override
204
- hasHandlerOverride(pathConfig) {
205
- return (pathConfig !== undefined &&
206
- "handlerOverride" in pathConfig &&
207
- typeof pathConfig.handlerOverride === "function");
208
- }
209
- // Helper to check if a path config is a standard path config
210
- isStandardPathConfig(pathConfig) {
211
- return pathConfig !== undefined && !this.hasHandlerOverride(pathConfig);
212
- }
213
193
  // Helper to parse API error response
214
194
  parseErrorResponse(errorInfo) {
215
195
  // If we can't parse data, use status text
@@ -275,8 +255,10 @@ class ZapierApiClient {
275
255
  };
276
256
  }
277
257
  // For a base URL that isn't a Zapier-inferred domain, use the whole base URL.
258
+ const baseUrl = new URL(this.options.baseUrl);
259
+ const fullPath = baseUrl.pathname.replace(/\/$/, "") + path;
278
260
  return {
279
- url: new URL(path, this.options.baseUrl),
261
+ url: new URL(fullPath, baseUrl.origin),
280
262
  pathConfig: config,
281
263
  };
282
264
  }
@@ -299,7 +281,7 @@ class ZapierApiClient {
299
281
  // session!
300
282
  const authToken = await this.getAuthToken();
301
283
  if (authToken) {
302
- const authHeaderName = this.isStandardPathConfig(pathConfig) && pathConfig.authHeader
284
+ const authHeaderName = pathConfig && pathConfig.authHeader
303
285
  ? pathConfig.authHeader
304
286
  : "Authorization";
305
287
  headers.set(authHeaderName, getAuthorizationHeader(authToken));
@@ -316,12 +298,16 @@ class ZapierApiClient {
316
298
  // Helper to perform HTTP requests with JSON handling
317
299
  async fetchJson(method, path, data, options = {}) {
318
300
  // Check if this path has a handler override
319
- const { pathConfig } = this.buildUrl(path, options.searchParams);
320
- if (this.hasHandlerOverride(pathConfig)) {
301
+ const routeMatch = matchRoute(method, path);
302
+ if (routeMatch) {
321
303
  // Invoke the handler instead of making an HTTP request
322
- // Pass searchParams for GET requests, data for other methods
323
- const handlerRequest = method === "GET" ? options.searchParams : data;
324
- return pathConfig.handlerOverride({
304
+ // Merge params from all sources: body, searchParams, and route params
305
+ const handlerRequest = {
306
+ ...(typeof data === "object" ? data : {}),
307
+ ...options.searchParams,
308
+ ...routeMatch.params,
309
+ };
310
+ return routeMatch.handler({
325
311
  request: handlerRequest,
326
312
  deps: {
327
313
  httpClient: this,
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client.methods.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.methods.test.d.ts","sourceRoot":"","sources":["../../src/api/client.methods.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,158 @@
1
+ import { describe, it, expect, vi, beforeEach, } from "vitest";
2
+ import { createZapierApi } from "./client";
3
+ import * as router from "./router";
4
+ vi.mock("./router");
5
+ describe("ZapierApiClient Methods & Routing", () => {
6
+ let mockFetch;
7
+ let mockMatchRoute;
8
+ beforeEach(() => {
9
+ vi.clearAllMocks();
10
+ mockFetch = vi.fn();
11
+ mockMatchRoute = vi.mocked(router.matchRoute);
12
+ // Default fetch implementation
13
+ mockFetch.mockResolvedValue({
14
+ ok: true,
15
+ status: 200,
16
+ json: async () => ({ data: "default-response" }),
17
+ });
18
+ });
19
+ const createClient = () => createZapierApi({
20
+ baseUrl: "https://api.zapier.com",
21
+ token: "test-token",
22
+ fetch: mockFetch,
23
+ });
24
+ describe("GET requests", () => {
25
+ it("should route GET requests to handler when matched", async () => {
26
+ const client = createClient();
27
+ const mockHandler = vi.fn().mockResolvedValue({ id: 1, name: "Test" });
28
+ mockMatchRoute.mockReturnValue({
29
+ handler: mockHandler,
30
+ params: { id: "123" },
31
+ });
32
+ const result = await client.get("/api/resource/123", {
33
+ searchParams: { include: "details" },
34
+ });
35
+ expect(mockMatchRoute).toHaveBeenCalledWith("GET", "/api/resource/123");
36
+ expect(mockHandler).toHaveBeenCalledWith({
37
+ request: {
38
+ id: "123",
39
+ include: "details",
40
+ },
41
+ deps: expect.objectContaining({
42
+ httpClient: expect.any(Object),
43
+ }),
44
+ });
45
+ expect(result).toEqual({ id: 1, name: "Test" });
46
+ expect(mockFetch).not.toHaveBeenCalled();
47
+ });
48
+ it("should fallback to plain fetch when no route matches", async () => {
49
+ const client = createClient();
50
+ mockMatchRoute.mockReturnValue(null);
51
+ await client.get("/api/unknown");
52
+ expect(mockMatchRoute).toHaveBeenCalledWith("GET", "/api/unknown");
53
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/api/unknown"), expect.objectContaining({
54
+ method: "GET",
55
+ }));
56
+ });
57
+ });
58
+ describe("POST requests", () => {
59
+ it("should route POST requests to handler with body", async () => {
60
+ const client = createClient();
61
+ const mockHandler = vi.fn().mockResolvedValue({ success: true });
62
+ const body = { name: "New Item" };
63
+ mockMatchRoute.mockReturnValue({
64
+ handler: mockHandler,
65
+ params: {},
66
+ });
67
+ await client.post("/api/resource", body);
68
+ expect(mockMatchRoute).toHaveBeenCalledWith("POST", "/api/resource");
69
+ expect(mockHandler).toHaveBeenCalledWith({
70
+ request: {
71
+ name: "New Item",
72
+ },
73
+ deps: expect.objectContaining({
74
+ httpClient: expect.any(Object),
75
+ }),
76
+ });
77
+ expect(mockFetch).not.toHaveBeenCalled();
78
+ });
79
+ it("should merge body, params, and query params for handler", async () => {
80
+ const client = createClient();
81
+ const mockHandler = vi.fn().mockResolvedValue({ success: true });
82
+ const body = { name: "Updated Item" };
83
+ mockMatchRoute.mockReturnValue({
84
+ handler: mockHandler,
85
+ params: { id: "456" },
86
+ });
87
+ await client.post("/api/resource/456", body, {
88
+ searchParams: { version: "2" },
89
+ });
90
+ expect(mockHandler).toHaveBeenCalledWith({
91
+ request: {
92
+ name: "Updated Item", // from body
93
+ id: "456", // from route params
94
+ version: "2", // from query params
95
+ },
96
+ deps: expect.any(Object),
97
+ });
98
+ });
99
+ });
100
+ describe("PUT requests", () => {
101
+ it("should route PUT requests to handler", async () => {
102
+ const client = createClient();
103
+ const mockHandler = vi.fn().mockResolvedValue({ updated: true });
104
+ const body = { status: "active" };
105
+ mockMatchRoute.mockReturnValue({
106
+ handler: mockHandler,
107
+ params: { id: "789" },
108
+ });
109
+ await client.put("/api/resource/789", body);
110
+ expect(mockMatchRoute).toHaveBeenCalledWith("PUT", "/api/resource/789");
111
+ expect(mockHandler).toHaveBeenCalledWith({
112
+ request: {
113
+ status: "active",
114
+ id: "789",
115
+ },
116
+ deps: expect.any(Object),
117
+ });
118
+ });
119
+ });
120
+ describe("DELETE requests", () => {
121
+ it("should route DELETE requests to handler", async () => {
122
+ const client = createClient();
123
+ const mockHandler = vi.fn().mockResolvedValue({ deleted: true });
124
+ mockMatchRoute.mockReturnValue({
125
+ handler: mockHandler,
126
+ params: { id: "999" },
127
+ });
128
+ await client.delete("/api/resource/999");
129
+ expect(mockMatchRoute).toHaveBeenCalledWith("DELETE", "/api/resource/999");
130
+ expect(mockHandler).toHaveBeenCalledWith({
131
+ request: {
132
+ id: "999",
133
+ },
134
+ deps: expect.any(Object),
135
+ });
136
+ });
137
+ });
138
+ describe("Header handling", () => {
139
+ it("should pass headers to plain fetch but NOT to handler request object (by default)", async () => {
140
+ const client = createClient();
141
+ const mockHandler = vi.fn().mockResolvedValue({});
142
+ mockMatchRoute.mockReturnValue({
143
+ handler: mockHandler,
144
+ params: {},
145
+ });
146
+ await client.get("/api/test", {
147
+ headers: { "X-Custom": "Value" },
148
+ });
149
+ // The handler request object only includes params/body, not headers
150
+ // based on client.ts implementation:
151
+ // const handlerRequest = { ...data, ...options.searchParams, ...routeMatch.params };
152
+ expect(mockHandler).toHaveBeenCalledWith({
153
+ request: {},
154
+ deps: expect.any(Object),
155
+ });
156
+ });
157
+ });
158
+ });