@zapier/zapier-sdk 0.41.2 → 0.42.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.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -0
- package/dist/index.cjs +192 -61
- package/dist/index.d.mts +11 -16
- package/dist/index.mjs +192 -61
- package/dist/plugins/fetch/index.d.ts +12 -17
- package/dist/plugins/fetch/index.d.ts.map +1 -1
- package/dist/plugins/fetch/index.js +84 -24
- package/dist/plugins/fetch/schemas.d.ts +8 -0
- package/dist/plugins/fetch/schemas.d.ts.map +1 -1
- package/dist/plugins/fetch/schemas.js +21 -12
- package/dist/utils/abort-utils.d.ts +64 -0
- package/dist/utils/abort-utils.d.ts.map +1 -0
- package/dist/utils/abort-utils.js +107 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -971,6 +971,7 @@ Make authenticated HTTP requests to any API through Zapier. Pass a connectionId
|
|
|
971
971
|
| ↳ `body` | `string, custom, record` | ❌ | — | — | Request body — plain objects and JSON strings are auto-detected and Content-Type is set accordingly |
|
|
972
972
|
| ↳ `connection` | `string, number` | ❌ | — | — | Connection alias (string) or numeric connectionId. Strings are resolved from the connections map; numbers are used directly. |
|
|
973
973
|
| ↳ `callbackUrl` | `string` | ❌ | — | — | URL to send async response to (makes request async) |
|
|
974
|
+
| ↳ `maxTime` | `number` | ❌ | — | — | Maximum seconds to wait for a response. Honored on a best-effort basis; the server may silently enforce a lower ceiling. |
|
|
974
975
|
|
|
975
976
|
**Returns:** `Promise<Response>`
|
|
976
977
|
|
package/dist/index.cjs
CHANGED
|
@@ -544,6 +544,20 @@ var appsPlugin = ({ sdk }) => {
|
|
|
544
544
|
var FetchUrlSchema = zod.z.union([zod.z.string(), zod.z.instanceof(URL)]).describe(
|
|
545
545
|
"The full URL of the API endpoint to call (proxied through Zapier's Relay service)"
|
|
546
546
|
);
|
|
547
|
+
var FetchInitZapierFieldsSchema = zod.z.object({
|
|
548
|
+
connectionId: ConnectionIdPropertySchema.optional().meta({
|
|
549
|
+
deprecated: true
|
|
550
|
+
}),
|
|
551
|
+
connection: ConnectionPropertySchema.optional(),
|
|
552
|
+
/** @deprecated Use connection instead */
|
|
553
|
+
authenticationId: AuthenticationIdPropertySchema.optional().meta({
|
|
554
|
+
deprecated: true
|
|
555
|
+
}),
|
|
556
|
+
callbackUrl: zod.z.string().optional().describe("URL to send async response to (makes request async)"),
|
|
557
|
+
maxTime: zod.z.number().int().positive().optional().describe(
|
|
558
|
+
"Maximum seconds to wait for a response. Honored on a best-effort basis; the server may silently enforce a lower ceiling."
|
|
559
|
+
)
|
|
560
|
+
});
|
|
547
561
|
var FetchInitSchema = zod.z.object({
|
|
548
562
|
method: zod.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]).optional().describe("HTTP method for the request (defaults to GET)"),
|
|
549
563
|
headers: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("HTTP headers to include in the request"),
|
|
@@ -554,17 +568,8 @@ var FetchInitSchema = zod.z.object({
|
|
|
554
568
|
zod.z.record(zod.z.string(), zod.z.unknown())
|
|
555
569
|
]).optional().describe(
|
|
556
570
|
"Request body \u2014 plain objects and JSON strings are auto-detected and Content-Type is set accordingly"
|
|
557
|
-
)
|
|
558
|
-
|
|
559
|
-
deprecated: true
|
|
560
|
-
}),
|
|
561
|
-
connection: ConnectionPropertySchema.optional(),
|
|
562
|
-
/** @deprecated Use connection instead */
|
|
563
|
-
authenticationId: AuthenticationIdPropertySchema.optional().meta({
|
|
564
|
-
deprecated: true
|
|
565
|
-
}),
|
|
566
|
-
callbackUrl: zod.z.string().optional().describe("URL to send async response to (makes request async)")
|
|
567
|
-
}).optional().describe(
|
|
571
|
+
)
|
|
572
|
+
}).extend(FetchInitZapierFieldsSchema.shape).optional().describe(
|
|
568
573
|
"Request options including method, headers, body, and authentication"
|
|
569
574
|
).meta({
|
|
570
575
|
aliases: { connectionId: "connection", authenticationId: "connection" }
|
|
@@ -717,6 +722,82 @@ function isResolvedAppLocator(appLocator) {
|
|
|
717
722
|
return !!appLocator.implementationName;
|
|
718
723
|
}
|
|
719
724
|
|
|
725
|
+
// src/utils/abort-utils.ts
|
|
726
|
+
function getAbortSignalApi() {
|
|
727
|
+
const api = AbortSignal;
|
|
728
|
+
return { any: api.any };
|
|
729
|
+
}
|
|
730
|
+
function createTimeoutError(timeoutMs) {
|
|
731
|
+
if (typeof DOMException !== "undefined") {
|
|
732
|
+
return new DOMException(
|
|
733
|
+
`The operation timed out after ${timeoutMs}ms`,
|
|
734
|
+
"TimeoutError"
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
const error = new Error(`The operation timed out after ${timeoutMs}ms`);
|
|
738
|
+
error.name = "TimeoutError";
|
|
739
|
+
return error;
|
|
740
|
+
}
|
|
741
|
+
function createTimeoutAbortSignal({
|
|
742
|
+
timeoutMs
|
|
743
|
+
}) {
|
|
744
|
+
const controller = new AbortController();
|
|
745
|
+
let timer = setTimeout(() => {
|
|
746
|
+
timer = void 0;
|
|
747
|
+
controller.abort(createTimeoutError(timeoutMs));
|
|
748
|
+
}, timeoutMs);
|
|
749
|
+
const maybeUnref = timer.unref;
|
|
750
|
+
if (typeof maybeUnref === "function") {
|
|
751
|
+
maybeUnref.call(timer);
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
signal: controller.signal,
|
|
755
|
+
dispose: () => {
|
|
756
|
+
if (timer !== void 0) {
|
|
757
|
+
clearTimeout(timer);
|
|
758
|
+
timer = void 0;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
function combineAbortSignals({
|
|
764
|
+
handles,
|
|
765
|
+
abortSignalApi = getAbortSignalApi()
|
|
766
|
+
}) {
|
|
767
|
+
if (handles.length === 0) return void 0;
|
|
768
|
+
if (handles.length === 1) return handles[0];
|
|
769
|
+
const disposeInputs = () => {
|
|
770
|
+
for (const handle of handles) handle.dispose();
|
|
771
|
+
};
|
|
772
|
+
if (abortSignalApi.any) {
|
|
773
|
+
return {
|
|
774
|
+
signal: abortSignalApi.any(handles.map((h) => h.signal)),
|
|
775
|
+
dispose: disposeInputs
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
const controller = new AbortController();
|
|
779
|
+
const listeners = [];
|
|
780
|
+
for (const { signal: source } of handles) {
|
|
781
|
+
if (source.aborted) {
|
|
782
|
+
controller.abort(source.reason);
|
|
783
|
+
return { signal: controller.signal, dispose: disposeInputs };
|
|
784
|
+
}
|
|
785
|
+
const handler = () => controller.abort(source.reason);
|
|
786
|
+
source.addEventListener("abort", handler, { once: true });
|
|
787
|
+
listeners.push({ source, handler });
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
signal: controller.signal,
|
|
791
|
+
dispose: () => {
|
|
792
|
+
for (const { source, handler } of listeners) {
|
|
793
|
+
source.removeEventListener("abort", handler);
|
|
794
|
+
}
|
|
795
|
+
listeners.length = 0;
|
|
796
|
+
disposeInputs();
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
720
801
|
// src/utils/type-guard-utils.ts
|
|
721
802
|
function isPlainObject(value) {
|
|
722
803
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -752,6 +833,34 @@ function getMethodMetadata() {
|
|
|
752
833
|
return telemetryStore.getStore()?.methodMetadata;
|
|
753
834
|
}
|
|
754
835
|
|
|
836
|
+
// src/utils/validation.ts
|
|
837
|
+
var validate = (schema, input) => {
|
|
838
|
+
const result = schema.safeParse(input);
|
|
839
|
+
if (!result.success) {
|
|
840
|
+
const errorMessages = result.error.issues.map((error) => {
|
|
841
|
+
const path = error.path.length > 0 ? error.path.join(".") : "input";
|
|
842
|
+
return `${path}: ${error.message}`;
|
|
843
|
+
});
|
|
844
|
+
const message = `Validation failed:
|
|
845
|
+
${errorMessages.join("\n ")}`;
|
|
846
|
+
throw new ZapierValidationError(message, {
|
|
847
|
+
details: {
|
|
848
|
+
zodErrors: result.error.issues,
|
|
849
|
+
input
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
return result.data;
|
|
854
|
+
};
|
|
855
|
+
function createValidator(schema) {
|
|
856
|
+
return function validateFn(input) {
|
|
857
|
+
return validate(schema, input);
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
var validateOptions = (schema, options) => {
|
|
861
|
+
return validate(schema, options);
|
|
862
|
+
};
|
|
863
|
+
|
|
755
864
|
// src/plugins/fetch/index.ts
|
|
756
865
|
function transformUrlToRelayPath(url) {
|
|
757
866
|
const targetUrl = new URL(url);
|
|
@@ -789,6 +898,35 @@ function inferContentType(body) {
|
|
|
789
898
|
}
|
|
790
899
|
return void 0;
|
|
791
900
|
}
|
|
901
|
+
function rewrapIfMaxTimeTimeout({
|
|
902
|
+
error,
|
|
903
|
+
abortSignal,
|
|
904
|
+
maxTimeSeconds
|
|
905
|
+
}) {
|
|
906
|
+
if (maxTimeSeconds === void 0 || !abortSignal?.aborted) return error;
|
|
907
|
+
const reason = abortSignal.reason;
|
|
908
|
+
if (!reason || reason.name !== "TimeoutError") return error;
|
|
909
|
+
return new ZapierTimeoutError(
|
|
910
|
+
`fetch timed out after ${maxTimeSeconds}s (maxTime)`,
|
|
911
|
+
{ cause: error }
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
function buildAbortHandle({
|
|
915
|
+
maxTimeSeconds,
|
|
916
|
+
callerSignal
|
|
917
|
+
}) {
|
|
918
|
+
const handles = [];
|
|
919
|
+
if (callerSignal) handles.push({ signal: callerSignal, dispose: () => {
|
|
920
|
+
} });
|
|
921
|
+
if (maxTimeSeconds !== void 0) {
|
|
922
|
+
handles.push(
|
|
923
|
+
createTimeoutAbortSignal({ timeoutMs: maxTimeSeconds * 1e3 })
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
return combineAbortSignals({ handles });
|
|
927
|
+
}
|
|
928
|
+
var FetchInitSdkValidationSchema = zod.z.looseObject(FetchInitZapierFieldsSchema.shape).optional();
|
|
929
|
+
var validateFetchInit = createValidator(FetchInitSdkValidationSchema);
|
|
792
930
|
var fetchPlugin = ({ context }) => {
|
|
793
931
|
return {
|
|
794
932
|
fetch: async function fetch2(url, init) {
|
|
@@ -797,11 +935,15 @@ var fetchPlugin = ({ context }) => {
|
|
|
797
935
|
const startTime = Date.now();
|
|
798
936
|
const isNested = isTelemetryNested();
|
|
799
937
|
try {
|
|
938
|
+
if (init) {
|
|
939
|
+
validateFetchInit(init);
|
|
940
|
+
}
|
|
800
941
|
const {
|
|
801
942
|
connectionId,
|
|
802
943
|
connection,
|
|
803
944
|
authenticationId,
|
|
804
945
|
callbackUrl,
|
|
946
|
+
maxTime,
|
|
805
947
|
...fetchInit
|
|
806
948
|
} = init || {};
|
|
807
949
|
const resolvedConnectionId = await resolveConnectionId({
|
|
@@ -832,32 +974,49 @@ var fetchPlugin = ({ context }) => {
|
|
|
832
974
|
if (callbackUrl) {
|
|
833
975
|
headers["X-Relay-Callback-Url"] = callbackUrl;
|
|
834
976
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
authRequired: true
|
|
977
|
+
if (maxTime !== void 0) {
|
|
978
|
+
headers["X-Zapier-Sdk-Max-Time"] = String(maxTime);
|
|
979
|
+
}
|
|
980
|
+
const abortHandle = buildAbortHandle({
|
|
981
|
+
maxTimeSeconds: maxTime,
|
|
982
|
+
callerSignal: fetchInit.signal
|
|
842
983
|
});
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
984
|
+
try {
|
|
985
|
+
const result = await api.fetch(relayPath, {
|
|
986
|
+
method: fetchInit.method ?? "GET",
|
|
987
|
+
body: fetchInit.body,
|
|
988
|
+
headers,
|
|
989
|
+
redirect: fetchInit.redirect,
|
|
990
|
+
signal: abortHandle?.signal,
|
|
991
|
+
authRequired: true
|
|
847
992
|
});
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
993
|
+
const relayError = result.headers.get("x-relay-error");
|
|
994
|
+
if (relayError) {
|
|
995
|
+
throw new ZapierRelayError(relayError, {
|
|
996
|
+
statusCode: result.status
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
if (!isNested) {
|
|
1000
|
+
context.eventEmission.emitMethodCalled({
|
|
1001
|
+
method_name: "fetch",
|
|
1002
|
+
execution_duration_ms: Date.now() - startTime,
|
|
1003
|
+
success_flag: true,
|
|
1004
|
+
error_message: null,
|
|
1005
|
+
error_type: null,
|
|
1006
|
+
argument_count: init ? 2 : 1,
|
|
1007
|
+
is_paginated: false
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
return result;
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
throw rewrapIfMaxTimeTimeout({
|
|
1013
|
+
error,
|
|
1014
|
+
abortSignal: abortHandle?.signal,
|
|
1015
|
+
maxTimeSeconds: maxTime
|
|
858
1016
|
});
|
|
1017
|
+
} finally {
|
|
1018
|
+
abortHandle?.dispose();
|
|
859
1019
|
}
|
|
860
|
-
return result;
|
|
861
1020
|
} catch (error) {
|
|
862
1021
|
if (!isNested) {
|
|
863
1022
|
context.eventEmission.emitMethodCalled({
|
|
@@ -1145,34 +1304,6 @@ function toIterable(source) {
|
|
|
1145
1304
|
return { [Symbol.asyncIterator]: () => source[Symbol.asyncIterator]() };
|
|
1146
1305
|
}
|
|
1147
1306
|
|
|
1148
|
-
// src/utils/validation.ts
|
|
1149
|
-
var validate = (schema, input) => {
|
|
1150
|
-
const result = schema.safeParse(input);
|
|
1151
|
-
if (!result.success) {
|
|
1152
|
-
const errorMessages = result.error.issues.map((error) => {
|
|
1153
|
-
const path = error.path.length > 0 ? error.path.join(".") : "input";
|
|
1154
|
-
return `${path}: ${error.message}`;
|
|
1155
|
-
});
|
|
1156
|
-
const message = `Validation failed:
|
|
1157
|
-
${errorMessages.join("\n ")}`;
|
|
1158
|
-
throw new ZapierValidationError(message, {
|
|
1159
|
-
details: {
|
|
1160
|
-
zodErrors: result.error.issues,
|
|
1161
|
-
input
|
|
1162
|
-
}
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
return result.data;
|
|
1166
|
-
};
|
|
1167
|
-
function createValidator(schema) {
|
|
1168
|
-
return function validateFn(input) {
|
|
1169
|
-
return validate(schema, input);
|
|
1170
|
-
};
|
|
1171
|
-
}
|
|
1172
|
-
var validateOptions = (schema, options) => {
|
|
1173
|
-
return validate(schema, options);
|
|
1174
|
-
};
|
|
1175
|
-
|
|
1176
1307
|
// src/utils/function-utils.ts
|
|
1177
1308
|
function extractCursor(data) {
|
|
1178
1309
|
if (!data?.next) {
|
package/dist/index.d.mts
CHANGED
|
@@ -1597,15 +1597,18 @@ interface ConnectionsPluginProvides {
|
|
|
1597
1597
|
}
|
|
1598
1598
|
declare const connectionsPlugin: Plugin<{}, ManifestPluginProvides["context"], ConnectionsPluginProvides>;
|
|
1599
1599
|
|
|
1600
|
+
interface ZapierFetchInitOptions extends RequestInit {
|
|
1601
|
+
/** @deprecated Use `connection` instead. */
|
|
1602
|
+
connectionId?: string | number;
|
|
1603
|
+
connection?: string | number;
|
|
1604
|
+
/** @deprecated Use `connection` instead. */
|
|
1605
|
+
authenticationId?: string | number;
|
|
1606
|
+
callbackUrl?: string;
|
|
1607
|
+
/** Maximum seconds to wait for a response, subject to a server-side limit. */
|
|
1608
|
+
maxTime?: number;
|
|
1609
|
+
}
|
|
1600
1610
|
interface FetchPluginProvides {
|
|
1601
|
-
fetch: (url: string | URL, init?:
|
|
1602
|
-
/** @deprecated Use `connection` instead. */
|
|
1603
|
-
connectionId?: string | number;
|
|
1604
|
-
connection?: string | number;
|
|
1605
|
-
/** @deprecated Use `connection` instead. */
|
|
1606
|
-
authenticationId?: string | number;
|
|
1607
|
-
callbackUrl?: string;
|
|
1608
|
-
}) => Promise<Response>;
|
|
1611
|
+
fetch: (url: string | URL, init?: ZapierFetchInitOptions) => Promise<Response>;
|
|
1609
1612
|
context: {
|
|
1610
1613
|
meta: {
|
|
1611
1614
|
fetch: {
|
|
@@ -1631,14 +1634,6 @@ declare const fetchPlugin: Plugin<{}, // no SDK dependencies
|
|
|
1631
1634
|
api: ApiClient;
|
|
1632
1635
|
} & ConnectionsPluginProvides["context"] & EventEmissionContext, // requires api, connections, and eventEmission in context
|
|
1633
1636
|
FetchPluginProvides>;
|
|
1634
|
-
type ZapierFetchInitOptions = RequestInit & {
|
|
1635
|
-
/** @deprecated Use `connection` instead. */
|
|
1636
|
-
connectionId?: string | number;
|
|
1637
|
-
connection?: string | number;
|
|
1638
|
-
/** @deprecated Use `connection` instead. */
|
|
1639
|
-
authenticationId?: string | number;
|
|
1640
|
-
callbackUrl?: string;
|
|
1641
|
-
};
|
|
1642
1637
|
|
|
1643
1638
|
declare const RunActionSchema: z.ZodObject<{
|
|
1644
1639
|
app: z.ZodString & {
|
package/dist/index.mjs
CHANGED
|
@@ -542,6 +542,20 @@ var appsPlugin = ({ sdk }) => {
|
|
|
542
542
|
var FetchUrlSchema = z.union([z.string(), z.instanceof(URL)]).describe(
|
|
543
543
|
"The full URL of the API endpoint to call (proxied through Zapier's Relay service)"
|
|
544
544
|
);
|
|
545
|
+
var FetchInitZapierFieldsSchema = z.object({
|
|
546
|
+
connectionId: ConnectionIdPropertySchema.optional().meta({
|
|
547
|
+
deprecated: true
|
|
548
|
+
}),
|
|
549
|
+
connection: ConnectionPropertySchema.optional(),
|
|
550
|
+
/** @deprecated Use connection instead */
|
|
551
|
+
authenticationId: AuthenticationIdPropertySchema.optional().meta({
|
|
552
|
+
deprecated: true
|
|
553
|
+
}),
|
|
554
|
+
callbackUrl: z.string().optional().describe("URL to send async response to (makes request async)"),
|
|
555
|
+
maxTime: z.number().int().positive().optional().describe(
|
|
556
|
+
"Maximum seconds to wait for a response. Honored on a best-effort basis; the server may silently enforce a lower ceiling."
|
|
557
|
+
)
|
|
558
|
+
});
|
|
545
559
|
var FetchInitSchema = z.object({
|
|
546
560
|
method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]).optional().describe("HTTP method for the request (defaults to GET)"),
|
|
547
561
|
headers: z.record(z.string(), z.string()).optional().describe("HTTP headers to include in the request"),
|
|
@@ -552,17 +566,8 @@ var FetchInitSchema = z.object({
|
|
|
552
566
|
z.record(z.string(), z.unknown())
|
|
553
567
|
]).optional().describe(
|
|
554
568
|
"Request body \u2014 plain objects and JSON strings are auto-detected and Content-Type is set accordingly"
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
deprecated: true
|
|
558
|
-
}),
|
|
559
|
-
connection: ConnectionPropertySchema.optional(),
|
|
560
|
-
/** @deprecated Use connection instead */
|
|
561
|
-
authenticationId: AuthenticationIdPropertySchema.optional().meta({
|
|
562
|
-
deprecated: true
|
|
563
|
-
}),
|
|
564
|
-
callbackUrl: z.string().optional().describe("URL to send async response to (makes request async)")
|
|
565
|
-
}).optional().describe(
|
|
569
|
+
)
|
|
570
|
+
}).extend(FetchInitZapierFieldsSchema.shape).optional().describe(
|
|
566
571
|
"Request options including method, headers, body, and authentication"
|
|
567
572
|
).meta({
|
|
568
573
|
aliases: { connectionId: "connection", authenticationId: "connection" }
|
|
@@ -715,6 +720,82 @@ function isResolvedAppLocator(appLocator) {
|
|
|
715
720
|
return !!appLocator.implementationName;
|
|
716
721
|
}
|
|
717
722
|
|
|
723
|
+
// src/utils/abort-utils.ts
|
|
724
|
+
function getAbortSignalApi() {
|
|
725
|
+
const api = AbortSignal;
|
|
726
|
+
return { any: api.any };
|
|
727
|
+
}
|
|
728
|
+
function createTimeoutError(timeoutMs) {
|
|
729
|
+
if (typeof DOMException !== "undefined") {
|
|
730
|
+
return new DOMException(
|
|
731
|
+
`The operation timed out after ${timeoutMs}ms`,
|
|
732
|
+
"TimeoutError"
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
const error = new Error(`The operation timed out after ${timeoutMs}ms`);
|
|
736
|
+
error.name = "TimeoutError";
|
|
737
|
+
return error;
|
|
738
|
+
}
|
|
739
|
+
function createTimeoutAbortSignal({
|
|
740
|
+
timeoutMs
|
|
741
|
+
}) {
|
|
742
|
+
const controller = new AbortController();
|
|
743
|
+
let timer = setTimeout(() => {
|
|
744
|
+
timer = void 0;
|
|
745
|
+
controller.abort(createTimeoutError(timeoutMs));
|
|
746
|
+
}, timeoutMs);
|
|
747
|
+
const maybeUnref = timer.unref;
|
|
748
|
+
if (typeof maybeUnref === "function") {
|
|
749
|
+
maybeUnref.call(timer);
|
|
750
|
+
}
|
|
751
|
+
return {
|
|
752
|
+
signal: controller.signal,
|
|
753
|
+
dispose: () => {
|
|
754
|
+
if (timer !== void 0) {
|
|
755
|
+
clearTimeout(timer);
|
|
756
|
+
timer = void 0;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function combineAbortSignals({
|
|
762
|
+
handles,
|
|
763
|
+
abortSignalApi = getAbortSignalApi()
|
|
764
|
+
}) {
|
|
765
|
+
if (handles.length === 0) return void 0;
|
|
766
|
+
if (handles.length === 1) return handles[0];
|
|
767
|
+
const disposeInputs = () => {
|
|
768
|
+
for (const handle of handles) handle.dispose();
|
|
769
|
+
};
|
|
770
|
+
if (abortSignalApi.any) {
|
|
771
|
+
return {
|
|
772
|
+
signal: abortSignalApi.any(handles.map((h) => h.signal)),
|
|
773
|
+
dispose: disposeInputs
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
const controller = new AbortController();
|
|
777
|
+
const listeners = [];
|
|
778
|
+
for (const { signal: source } of handles) {
|
|
779
|
+
if (source.aborted) {
|
|
780
|
+
controller.abort(source.reason);
|
|
781
|
+
return { signal: controller.signal, dispose: disposeInputs };
|
|
782
|
+
}
|
|
783
|
+
const handler = () => controller.abort(source.reason);
|
|
784
|
+
source.addEventListener("abort", handler, { once: true });
|
|
785
|
+
listeners.push({ source, handler });
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
signal: controller.signal,
|
|
789
|
+
dispose: () => {
|
|
790
|
+
for (const { source, handler } of listeners) {
|
|
791
|
+
source.removeEventListener("abort", handler);
|
|
792
|
+
}
|
|
793
|
+
listeners.length = 0;
|
|
794
|
+
disposeInputs();
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
718
799
|
// src/utils/type-guard-utils.ts
|
|
719
800
|
function isPlainObject(value) {
|
|
720
801
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -750,6 +831,34 @@ function getMethodMetadata() {
|
|
|
750
831
|
return telemetryStore.getStore()?.methodMetadata;
|
|
751
832
|
}
|
|
752
833
|
|
|
834
|
+
// src/utils/validation.ts
|
|
835
|
+
var validate = (schema, input) => {
|
|
836
|
+
const result = schema.safeParse(input);
|
|
837
|
+
if (!result.success) {
|
|
838
|
+
const errorMessages = result.error.issues.map((error) => {
|
|
839
|
+
const path = error.path.length > 0 ? error.path.join(".") : "input";
|
|
840
|
+
return `${path}: ${error.message}`;
|
|
841
|
+
});
|
|
842
|
+
const message = `Validation failed:
|
|
843
|
+
${errorMessages.join("\n ")}`;
|
|
844
|
+
throw new ZapierValidationError(message, {
|
|
845
|
+
details: {
|
|
846
|
+
zodErrors: result.error.issues,
|
|
847
|
+
input
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
return result.data;
|
|
852
|
+
};
|
|
853
|
+
function createValidator(schema) {
|
|
854
|
+
return function validateFn(input) {
|
|
855
|
+
return validate(schema, input);
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
var validateOptions = (schema, options) => {
|
|
859
|
+
return validate(schema, options);
|
|
860
|
+
};
|
|
861
|
+
|
|
753
862
|
// src/plugins/fetch/index.ts
|
|
754
863
|
function transformUrlToRelayPath(url) {
|
|
755
864
|
const targetUrl = new URL(url);
|
|
@@ -787,6 +896,35 @@ function inferContentType(body) {
|
|
|
787
896
|
}
|
|
788
897
|
return void 0;
|
|
789
898
|
}
|
|
899
|
+
function rewrapIfMaxTimeTimeout({
|
|
900
|
+
error,
|
|
901
|
+
abortSignal,
|
|
902
|
+
maxTimeSeconds
|
|
903
|
+
}) {
|
|
904
|
+
if (maxTimeSeconds === void 0 || !abortSignal?.aborted) return error;
|
|
905
|
+
const reason = abortSignal.reason;
|
|
906
|
+
if (!reason || reason.name !== "TimeoutError") return error;
|
|
907
|
+
return new ZapierTimeoutError(
|
|
908
|
+
`fetch timed out after ${maxTimeSeconds}s (maxTime)`,
|
|
909
|
+
{ cause: error }
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
function buildAbortHandle({
|
|
913
|
+
maxTimeSeconds,
|
|
914
|
+
callerSignal
|
|
915
|
+
}) {
|
|
916
|
+
const handles = [];
|
|
917
|
+
if (callerSignal) handles.push({ signal: callerSignal, dispose: () => {
|
|
918
|
+
} });
|
|
919
|
+
if (maxTimeSeconds !== void 0) {
|
|
920
|
+
handles.push(
|
|
921
|
+
createTimeoutAbortSignal({ timeoutMs: maxTimeSeconds * 1e3 })
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
return combineAbortSignals({ handles });
|
|
925
|
+
}
|
|
926
|
+
var FetchInitSdkValidationSchema = z.looseObject(FetchInitZapierFieldsSchema.shape).optional();
|
|
927
|
+
var validateFetchInit = createValidator(FetchInitSdkValidationSchema);
|
|
790
928
|
var fetchPlugin = ({ context }) => {
|
|
791
929
|
return {
|
|
792
930
|
fetch: async function fetch2(url, init) {
|
|
@@ -795,11 +933,15 @@ var fetchPlugin = ({ context }) => {
|
|
|
795
933
|
const startTime = Date.now();
|
|
796
934
|
const isNested = isTelemetryNested();
|
|
797
935
|
try {
|
|
936
|
+
if (init) {
|
|
937
|
+
validateFetchInit(init);
|
|
938
|
+
}
|
|
798
939
|
const {
|
|
799
940
|
connectionId,
|
|
800
941
|
connection,
|
|
801
942
|
authenticationId,
|
|
802
943
|
callbackUrl,
|
|
944
|
+
maxTime,
|
|
803
945
|
...fetchInit
|
|
804
946
|
} = init || {};
|
|
805
947
|
const resolvedConnectionId = await resolveConnectionId({
|
|
@@ -830,32 +972,49 @@ var fetchPlugin = ({ context }) => {
|
|
|
830
972
|
if (callbackUrl) {
|
|
831
973
|
headers["X-Relay-Callback-Url"] = callbackUrl;
|
|
832
974
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
authRequired: true
|
|
975
|
+
if (maxTime !== void 0) {
|
|
976
|
+
headers["X-Zapier-Sdk-Max-Time"] = String(maxTime);
|
|
977
|
+
}
|
|
978
|
+
const abortHandle = buildAbortHandle({
|
|
979
|
+
maxTimeSeconds: maxTime,
|
|
980
|
+
callerSignal: fetchInit.signal
|
|
840
981
|
});
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
982
|
+
try {
|
|
983
|
+
const result = await api.fetch(relayPath, {
|
|
984
|
+
method: fetchInit.method ?? "GET",
|
|
985
|
+
body: fetchInit.body,
|
|
986
|
+
headers,
|
|
987
|
+
redirect: fetchInit.redirect,
|
|
988
|
+
signal: abortHandle?.signal,
|
|
989
|
+
authRequired: true
|
|
845
990
|
});
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
991
|
+
const relayError = result.headers.get("x-relay-error");
|
|
992
|
+
if (relayError) {
|
|
993
|
+
throw new ZapierRelayError(relayError, {
|
|
994
|
+
statusCode: result.status
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
if (!isNested) {
|
|
998
|
+
context.eventEmission.emitMethodCalled({
|
|
999
|
+
method_name: "fetch",
|
|
1000
|
+
execution_duration_ms: Date.now() - startTime,
|
|
1001
|
+
success_flag: true,
|
|
1002
|
+
error_message: null,
|
|
1003
|
+
error_type: null,
|
|
1004
|
+
argument_count: init ? 2 : 1,
|
|
1005
|
+
is_paginated: false
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
return result;
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
throw rewrapIfMaxTimeTimeout({
|
|
1011
|
+
error,
|
|
1012
|
+
abortSignal: abortHandle?.signal,
|
|
1013
|
+
maxTimeSeconds: maxTime
|
|
856
1014
|
});
|
|
1015
|
+
} finally {
|
|
1016
|
+
abortHandle?.dispose();
|
|
857
1017
|
}
|
|
858
|
-
return result;
|
|
859
1018
|
} catch (error) {
|
|
860
1019
|
if (!isNested) {
|
|
861
1020
|
context.eventEmission.emitMethodCalled({
|
|
@@ -1143,34 +1302,6 @@ function toIterable(source) {
|
|
|
1143
1302
|
return { [Symbol.asyncIterator]: () => source[Symbol.asyncIterator]() };
|
|
1144
1303
|
}
|
|
1145
1304
|
|
|
1146
|
-
// src/utils/validation.ts
|
|
1147
|
-
var validate = (schema, input) => {
|
|
1148
|
-
const result = schema.safeParse(input);
|
|
1149
|
-
if (!result.success) {
|
|
1150
|
-
const errorMessages = result.error.issues.map((error) => {
|
|
1151
|
-
const path = error.path.length > 0 ? error.path.join(".") : "input";
|
|
1152
|
-
return `${path}: ${error.message}`;
|
|
1153
|
-
});
|
|
1154
|
-
const message = `Validation failed:
|
|
1155
|
-
${errorMessages.join("\n ")}`;
|
|
1156
|
-
throw new ZapierValidationError(message, {
|
|
1157
|
-
details: {
|
|
1158
|
-
zodErrors: result.error.issues,
|
|
1159
|
-
input
|
|
1160
|
-
}
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
return result.data;
|
|
1164
|
-
};
|
|
1165
|
-
function createValidator(schema) {
|
|
1166
|
-
return function validateFn(input) {
|
|
1167
|
-
return validate(schema, input);
|
|
1168
|
-
};
|
|
1169
|
-
}
|
|
1170
|
-
var validateOptions = (schema, options) => {
|
|
1171
|
-
return validate(schema, options);
|
|
1172
|
-
};
|
|
1173
|
-
|
|
1174
1305
|
// src/utils/function-utils.ts
|
|
1175
1306
|
function extractCursor(data) {
|
|
1176
1307
|
if (!data?.next) {
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import type { Plugin } from "../../types/plugin";
|
|
2
2
|
import type { ApiClient } from "../../api";
|
|
3
3
|
import type { ConnectionsPluginProvides } from "../connections";
|
|
4
|
-
import
|
|
4
|
+
import { z } from "zod";
|
|
5
5
|
import type { EventEmissionContext } from "../eventEmission";
|
|
6
|
+
export interface ZapierFetchInitOptions extends RequestInit {
|
|
7
|
+
/** @deprecated Use `connection` instead. */
|
|
8
|
+
connectionId?: string | number;
|
|
9
|
+
connection?: string | number;
|
|
10
|
+
/** @deprecated Use `connection` instead. */
|
|
11
|
+
authenticationId?: string | number;
|
|
12
|
+
callbackUrl?: string;
|
|
13
|
+
/** Maximum seconds to wait for a response, subject to a server-side limit. */
|
|
14
|
+
maxTime?: number;
|
|
15
|
+
}
|
|
6
16
|
export interface FetchPluginProvides {
|
|
7
|
-
fetch: (url: string | URL, init?:
|
|
8
|
-
/** @deprecated Use `connection` instead. */
|
|
9
|
-
connectionId?: string | number;
|
|
10
|
-
connection?: string | number;
|
|
11
|
-
/** @deprecated Use `connection` instead. */
|
|
12
|
-
authenticationId?: string | number;
|
|
13
|
-
callbackUrl?: string;
|
|
14
|
-
}) => Promise<Response>;
|
|
17
|
+
fetch: (url: string | URL, init?: ZapierFetchInitOptions) => Promise<Response>;
|
|
15
18
|
context: {
|
|
16
19
|
meta: {
|
|
17
20
|
fetch: {
|
|
@@ -37,12 +40,4 @@ export declare const fetchPlugin: Plugin<{}, // no SDK dependencies
|
|
|
37
40
|
api: ApiClient;
|
|
38
41
|
} & ConnectionsPluginProvides["context"] & EventEmissionContext, // requires api, connections, and eventEmission in context
|
|
39
42
|
FetchPluginProvides>;
|
|
40
|
-
export type ZapierFetchInitOptions = RequestInit & {
|
|
41
|
-
/** @deprecated Use `connection` instead. */
|
|
42
|
-
connectionId?: string | number;
|
|
43
|
-
connection?: string | number;
|
|
44
|
-
/** @deprecated Use `connection` instead. */
|
|
45
|
-
authenticationId?: string | number;
|
|
46
|
-
callbackUrl?: string;
|
|
47
|
-
};
|
|
48
43
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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,yBAAyB,EAAE,MAAM,gBAAgB,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,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAMhE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAgB7D,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4GD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CACL,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,IAAI,CAAC,EAAE,sBAAsB,KAC1B,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;AAQD;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAC9B,EAAE,EAAE,sBAAsB;AAC1B,AADI,sBAAsB;AAC1B;IAAE,GAAG,EAAE,SAAS,CAAA;CAAE,GAAG,yBAAyB,CAAC,SAAS,CAAC,GACvD,oBAAoB,EAAE,0DAA0D;AAClF,mBAAmB,CAiJpB,CAAC"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { ZapierRelayError } from "../../types/errors";
|
|
2
|
-
import { FetchUrlSchema, FetchInitSchema } from "./schemas";
|
|
1
|
+
import { ZapierRelayError, ZapierTimeoutError } from "../../types/errors";
|
|
2
|
+
import { FetchUrlSchema, FetchInitSchema, FetchInitZapierFieldsSchema, } from "./schemas";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
import { resolveConnectionId } from "../../utils/domain-utils";
|
|
5
|
+
import { combineAbortSignals, createTimeoutAbortSignal, } from "../../utils/abort-utils";
|
|
4
6
|
import { isPlainObject } from "../../utils/type-guard-utils";
|
|
5
7
|
// fetch() doesn't use createFunction/createPaginatedFunction (its signature
|
|
6
8
|
// doesn't fit the single-options-object pattern), so we wire up telemetry
|
|
7
9
|
// context tracking manually.
|
|
8
10
|
import { isTelemetryNested, runWithTelemetryContext, } from "../../utils/telemetry-context";
|
|
11
|
+
import { createValidator } from "../../utils/validation";
|
|
9
12
|
/**
|
|
10
13
|
* Transforms full URLs into Relay format: /relay/{domain}/{path}
|
|
11
14
|
*/
|
|
@@ -53,6 +56,41 @@ function inferContentType(body) {
|
|
|
53
56
|
}
|
|
54
57
|
return undefined;
|
|
55
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* If the request aborted because the client-side `maxTime` timer fired, rewrap the
|
|
61
|
+
* underlying error as a `ZapierTimeoutError` so callers (and telemetry) can
|
|
62
|
+
* distinguish it from a caller-triggered abort or an unrelated network
|
|
63
|
+
* cancellation. The original error is preserved as `cause`.
|
|
64
|
+
*
|
|
65
|
+
* Caller-triggered aborts are left unwrapped so the caller's own abort
|
|
66
|
+
* semantics (reason, name) propagate.
|
|
67
|
+
*/
|
|
68
|
+
function rewrapIfMaxTimeTimeout({ error, abortSignal, maxTimeSeconds, }) {
|
|
69
|
+
if (maxTimeSeconds === undefined || !abortSignal?.aborted)
|
|
70
|
+
return error;
|
|
71
|
+
const reason = abortSignal.reason;
|
|
72
|
+
if (!reason || reason.name !== "TimeoutError")
|
|
73
|
+
return error;
|
|
74
|
+
return new ZapierTimeoutError(`fetch timed out after ${maxTimeSeconds}s (maxTime)`, { cause: error });
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Builds an AbortSignal (and a dispose callback) from the optional maxTime
|
|
78
|
+
* timeout and an optional caller-provided signal. Callers must invoke
|
|
79
|
+
* `dispose()` when the request completes to release timers and listeners.
|
|
80
|
+
*/
|
|
81
|
+
function buildAbortHandle({ maxTimeSeconds, callerSignal, }) {
|
|
82
|
+
const handles = [];
|
|
83
|
+
if (callerSignal)
|
|
84
|
+
handles.push({ signal: callerSignal, dispose: () => { } });
|
|
85
|
+
if (maxTimeSeconds !== undefined) {
|
|
86
|
+
handles.push(createTimeoutAbortSignal({ timeoutMs: maxTimeSeconds * 1000 }));
|
|
87
|
+
}
|
|
88
|
+
return combineAbortSignals({ handles });
|
|
89
|
+
}
|
|
90
|
+
const FetchInitSdkValidationSchema = z
|
|
91
|
+
.looseObject(FetchInitZapierFieldsSchema.shape)
|
|
92
|
+
.optional();
|
|
93
|
+
const validateFetchInit = createValidator(FetchInitSdkValidationSchema);
|
|
56
94
|
/**
|
|
57
95
|
* Fetch plugin — the primary way to make authenticated HTTP requests through Zapier's Relay service.
|
|
58
96
|
* Mirrors the native fetch(url, init?) signature with additional Zapier-specific options.
|
|
@@ -65,7 +103,10 @@ export const fetchPlugin = ({ context }) => {
|
|
|
65
103
|
const startTime = Date.now();
|
|
66
104
|
const isNested = isTelemetryNested();
|
|
67
105
|
try {
|
|
68
|
-
|
|
106
|
+
if (init) {
|
|
107
|
+
validateFetchInit(init);
|
|
108
|
+
}
|
|
109
|
+
const { connectionId, connection, authenticationId, callbackUrl, maxTime, ...fetchInit } = init || {};
|
|
69
110
|
const resolvedConnectionId = await resolveConnectionId({
|
|
70
111
|
connectionId,
|
|
71
112
|
connection,
|
|
@@ -88,32 +129,51 @@ export const fetchPlugin = ({ context }) => {
|
|
|
88
129
|
if (callbackUrl) {
|
|
89
130
|
headers["X-Relay-Callback-Url"] = callbackUrl;
|
|
90
131
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
authRequired: true,
|
|
132
|
+
if (maxTime !== undefined) {
|
|
133
|
+
headers["X-Zapier-Sdk-Max-Time"] = String(maxTime);
|
|
134
|
+
}
|
|
135
|
+
const abortHandle = buildAbortHandle({
|
|
136
|
+
maxTimeSeconds: maxTime,
|
|
137
|
+
callerSignal: fetchInit.signal,
|
|
98
138
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
139
|
+
try {
|
|
140
|
+
const result = await api.fetch(relayPath, {
|
|
141
|
+
method: fetchInit.method ?? "GET",
|
|
142
|
+
body: fetchInit.body,
|
|
143
|
+
headers,
|
|
144
|
+
redirect: fetchInit.redirect,
|
|
145
|
+
signal: abortHandle?.signal,
|
|
146
|
+
authRequired: true,
|
|
103
147
|
});
|
|
148
|
+
const relayError = result.headers.get("x-relay-error");
|
|
149
|
+
if (relayError) {
|
|
150
|
+
throw new ZapierRelayError(relayError, {
|
|
151
|
+
statusCode: result.status,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (!isNested) {
|
|
155
|
+
context.eventEmission.emitMethodCalled({
|
|
156
|
+
method_name: "fetch",
|
|
157
|
+
execution_duration_ms: Date.now() - startTime,
|
|
158
|
+
success_flag: true,
|
|
159
|
+
error_message: null,
|
|
160
|
+
error_type: null,
|
|
161
|
+
argument_count: init ? 2 : 1,
|
|
162
|
+
is_paginated: false,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
104
166
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
error_message: null,
|
|
111
|
-
error_type: null,
|
|
112
|
-
argument_count: init ? 2 : 1,
|
|
113
|
-
is_paginated: false,
|
|
167
|
+
catch (error) {
|
|
168
|
+
throw rewrapIfMaxTimeTimeout({
|
|
169
|
+
error,
|
|
170
|
+
abortSignal: abortHandle?.signal,
|
|
171
|
+
maxTimeSeconds: maxTime,
|
|
114
172
|
});
|
|
115
173
|
}
|
|
116
|
-
|
|
174
|
+
finally {
|
|
175
|
+
abortHandle?.dispose();
|
|
176
|
+
}
|
|
117
177
|
}
|
|
118
178
|
catch (error) {
|
|
119
179
|
if (!isNested) {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const FetchUrlSchema: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<URL, URL>]>;
|
|
3
|
+
export declare const FetchInitZapierFieldsSchema: z.ZodObject<{
|
|
4
|
+
connectionId: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
5
|
+
connection: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
6
|
+
authenticationId: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
7
|
+
callbackUrl: z.ZodOptional<z.ZodString>;
|
|
8
|
+
maxTime: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
}, z.core.$strip>;
|
|
3
10
|
export declare const FetchInitSchema: z.ZodOptional<z.ZodObject<{
|
|
4
11
|
method: z.ZodOptional<z.ZodEnum<{
|
|
5
12
|
POST: "POST";
|
|
@@ -16,5 +23,6 @@ export declare const FetchInitSchema: z.ZodOptional<z.ZodObject<{
|
|
|
16
23
|
connection: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
17
24
|
authenticationId: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
18
25
|
callbackUrl: z.ZodOptional<z.ZodString>;
|
|
26
|
+
maxTime: z.ZodOptional<z.ZodNumber>;
|
|
19
27
|
}, z.core.$strip>>;
|
|
20
28
|
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,eAAO,MAAM,cAAc,2DAIxB,CAAC;AAEJ,eAAO,MAAM,2BAA2B;;;;;;iBAqBtC,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;kBA6BxB,CAAC"}
|
|
@@ -4,6 +4,26 @@ import { ConnectionIdPropertySchema, ConnectionPropertySchema, AuthenticationIdP
|
|
|
4
4
|
export const FetchUrlSchema = z
|
|
5
5
|
.union([z.string(), z.instanceof(URL)])
|
|
6
6
|
.describe("The full URL of the API endpoint to call (proxied through Zapier's Relay service)");
|
|
7
|
+
export const FetchInitZapierFieldsSchema = z.object({
|
|
8
|
+
connectionId: ConnectionIdPropertySchema.optional().meta({
|
|
9
|
+
deprecated: true,
|
|
10
|
+
}),
|
|
11
|
+
connection: ConnectionPropertySchema.optional(),
|
|
12
|
+
/** @deprecated Use connection instead */
|
|
13
|
+
authenticationId: AuthenticationIdPropertySchema.optional().meta({
|
|
14
|
+
deprecated: true,
|
|
15
|
+
}),
|
|
16
|
+
callbackUrl: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("URL to send async response to (makes request async)"),
|
|
20
|
+
maxTime: z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.positive()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Maximum seconds to wait for a response. Honored on a best-effort basis; the server may silently enforce a lower ceiling."),
|
|
26
|
+
});
|
|
7
27
|
export const FetchInitSchema = z
|
|
8
28
|
.object({
|
|
9
29
|
method: z
|
|
@@ -23,19 +43,8 @@ export const FetchInitSchema = z
|
|
|
23
43
|
])
|
|
24
44
|
.optional()
|
|
25
45
|
.describe("Request body — plain objects and JSON strings are auto-detected and Content-Type is set accordingly"),
|
|
26
|
-
connectionId: ConnectionIdPropertySchema.optional().meta({
|
|
27
|
-
deprecated: true,
|
|
28
|
-
}),
|
|
29
|
-
connection: ConnectionPropertySchema.optional(),
|
|
30
|
-
/** @deprecated Use connection instead */
|
|
31
|
-
authenticationId: AuthenticationIdPropertySchema.optional().meta({
|
|
32
|
-
deprecated: true,
|
|
33
|
-
}),
|
|
34
|
-
callbackUrl: z
|
|
35
|
-
.string()
|
|
36
|
-
.optional()
|
|
37
|
-
.describe("URL to send async response to (makes request async)"),
|
|
38
46
|
})
|
|
47
|
+
.extend(FetchInitZapierFieldsSchema.shape)
|
|
39
48
|
.optional()
|
|
40
49
|
.describe("Request options including method, headers, body, and authentication")
|
|
41
50
|
.meta({
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps the subset of `AbortSignal` static methods that `combineAbortSignals`
|
|
3
|
+
* needs. Serves two real uses:
|
|
4
|
+
*
|
|
5
|
+
* 1. Runtime feature detection. `AbortSignal.any()` isn't present on every
|
|
6
|
+
* platform we support (Node <19, older browsers, some sandboxed
|
|
7
|
+
* runtimes), so `combineAbortSignals` checks `any` at call time and
|
|
8
|
+
* falls back to manual listener wiring when it's missing.
|
|
9
|
+
* 2. Test injection. Tests pass a fake `abortSignalApi` to force a
|
|
10
|
+
* specific branch (native vs. fallback) regardless of what the current
|
|
11
|
+
* runtime actually supports.
|
|
12
|
+
*
|
|
13
|
+
* By default, `combineAbortSignals` reads the real globals via
|
|
14
|
+
* `getAbortSignalApi()`; callers rarely need to pass this explicitly.
|
|
15
|
+
*/
|
|
16
|
+
export interface AbortSignalApi {
|
|
17
|
+
any?: (signals: AbortSignal[]) => AbortSignal;
|
|
18
|
+
}
|
|
19
|
+
export interface DisposableAbortSignal {
|
|
20
|
+
signal: AbortSignal;
|
|
21
|
+
/**
|
|
22
|
+
* Release the resources backing this signal (timers, event listeners).
|
|
23
|
+
* Safe to call multiple times.
|
|
24
|
+
*/
|
|
25
|
+
dispose: () => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function createTimeoutError(timeoutMs: number): Error;
|
|
28
|
+
/**
|
|
29
|
+
* Creates an AbortSignal that fires after `timeoutMs` using a cancellable
|
|
30
|
+
* `AbortController` + `setTimeout`. We intentionally don't delegate to the
|
|
31
|
+
* native `AbortSignal.timeout()` — its returned signal has no way to cancel
|
|
32
|
+
* the underlying timer, which would make `dispose()` a lie on that path and
|
|
33
|
+
* give us divergent abort-reason messages across runtimes.
|
|
34
|
+
*
|
|
35
|
+
* On Node-like runtimes we `unref()` the timer so a pending timeout doesn't
|
|
36
|
+
* keep the process alive. In browsers `.unref` is absent and the guard is a
|
|
37
|
+
* harmless no-op.
|
|
38
|
+
*
|
|
39
|
+
* The signal's abort reason is a `DOMException` with `name: "TimeoutError"`
|
|
40
|
+
* so callers can distinguish timeout-driven aborts from caller-driven ones.
|
|
41
|
+
*
|
|
42
|
+
* Always call `dispose()` when the signal is no longer needed to clear the
|
|
43
|
+
* pending timer.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createTimeoutAbortSignal({ timeoutMs, }: {
|
|
46
|
+
timeoutMs: number;
|
|
47
|
+
}): DisposableAbortSignal;
|
|
48
|
+
/**
|
|
49
|
+
* Combines multiple disposable abort handles so the result aborts when *any*
|
|
50
|
+
* source does. Uses native `AbortSignal.any()` when available; otherwise wires
|
|
51
|
+
* up listeners manually.
|
|
52
|
+
*
|
|
53
|
+
* Returns `undefined` when the array is empty, or passes through a single
|
|
54
|
+
* handle without wrapping it.
|
|
55
|
+
*
|
|
56
|
+
* Always call `dispose()` on the result when it is no longer needed. This
|
|
57
|
+
* removes event listeners from source signals and calls `dispose()` on every
|
|
58
|
+
* input handle (releasing timers, etc).
|
|
59
|
+
*/
|
|
60
|
+
export declare function combineAbortSignals({ handles, abortSignalApi, }: {
|
|
61
|
+
handles: DisposableAbortSignal[];
|
|
62
|
+
abortSignalApi?: AbortSignalApi;
|
|
63
|
+
}): DisposableAbortSignal | undefined;
|
|
64
|
+
//# sourceMappingURL=abort-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort-utils.d.ts","sourceRoot":"","sources":["../../src/utils/abort-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,CAAC;CAC/C;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,WAAW,CAAC;IACpB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAOD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAW3D;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,SAAS,GACV,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,qBAAqB,CAuBxB;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,cAAoC,GACrC,EAAE;IACD,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC,GAAG,qBAAqB,GAAG,SAAS,CAwCpC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
function getAbortSignalApi() {
|
|
2
|
+
const api = AbortSignal;
|
|
3
|
+
return { any: api.any };
|
|
4
|
+
}
|
|
5
|
+
export function createTimeoutError(timeoutMs) {
|
|
6
|
+
if (typeof DOMException !== "undefined") {
|
|
7
|
+
return new DOMException(`The operation timed out after ${timeoutMs}ms`, "TimeoutError");
|
|
8
|
+
}
|
|
9
|
+
const error = new Error(`The operation timed out after ${timeoutMs}ms`);
|
|
10
|
+
error.name = "TimeoutError";
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// createTimeoutAbortSignal
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* Creates an AbortSignal that fires after `timeoutMs` using a cancellable
|
|
18
|
+
* `AbortController` + `setTimeout`. We intentionally don't delegate to the
|
|
19
|
+
* native `AbortSignal.timeout()` — its returned signal has no way to cancel
|
|
20
|
+
* the underlying timer, which would make `dispose()` a lie on that path and
|
|
21
|
+
* give us divergent abort-reason messages across runtimes.
|
|
22
|
+
*
|
|
23
|
+
* On Node-like runtimes we `unref()` the timer so a pending timeout doesn't
|
|
24
|
+
* keep the process alive. In browsers `.unref` is absent and the guard is a
|
|
25
|
+
* harmless no-op.
|
|
26
|
+
*
|
|
27
|
+
* The signal's abort reason is a `DOMException` with `name: "TimeoutError"`
|
|
28
|
+
* so callers can distinguish timeout-driven aborts from caller-driven ones.
|
|
29
|
+
*
|
|
30
|
+
* Always call `dispose()` when the signal is no longer needed to clear the
|
|
31
|
+
* pending timer.
|
|
32
|
+
*/
|
|
33
|
+
export function createTimeoutAbortSignal({ timeoutMs, }) {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
let timer = setTimeout(() => {
|
|
36
|
+
timer = undefined;
|
|
37
|
+
controller.abort(createTimeoutError(timeoutMs));
|
|
38
|
+
}, timeoutMs);
|
|
39
|
+
// Node (and Bun/Deno): don't let a pending timeout block process exit.
|
|
40
|
+
// Browsers: `unref` is undefined on the numeric handle; this is a no-op.
|
|
41
|
+
const maybeUnref = timer.unref;
|
|
42
|
+
if (typeof maybeUnref === "function") {
|
|
43
|
+
maybeUnref.call(timer);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
signal: controller.signal,
|
|
47
|
+
dispose: () => {
|
|
48
|
+
if (timer !== undefined) {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
timer = undefined;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// combineAbortSignals
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
/**
|
|
59
|
+
* Combines multiple disposable abort handles so the result aborts when *any*
|
|
60
|
+
* source does. Uses native `AbortSignal.any()` when available; otherwise wires
|
|
61
|
+
* up listeners manually.
|
|
62
|
+
*
|
|
63
|
+
* Returns `undefined` when the array is empty, or passes through a single
|
|
64
|
+
* handle without wrapping it.
|
|
65
|
+
*
|
|
66
|
+
* Always call `dispose()` on the result when it is no longer needed. This
|
|
67
|
+
* removes event listeners from source signals and calls `dispose()` on every
|
|
68
|
+
* input handle (releasing timers, etc).
|
|
69
|
+
*/
|
|
70
|
+
export function combineAbortSignals({ handles, abortSignalApi = getAbortSignalApi(), }) {
|
|
71
|
+
if (handles.length === 0)
|
|
72
|
+
return undefined;
|
|
73
|
+
if (handles.length === 1)
|
|
74
|
+
return handles[0];
|
|
75
|
+
const disposeInputs = () => {
|
|
76
|
+
for (const handle of handles)
|
|
77
|
+
handle.dispose();
|
|
78
|
+
};
|
|
79
|
+
if (abortSignalApi.any) {
|
|
80
|
+
return {
|
|
81
|
+
signal: abortSignalApi.any(handles.map((h) => h.signal)),
|
|
82
|
+
dispose: disposeInputs,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Fallback: wire up listeners manually.
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
const listeners = [];
|
|
88
|
+
for (const { signal: source } of handles) {
|
|
89
|
+
if (source.aborted) {
|
|
90
|
+
controller.abort(source.reason);
|
|
91
|
+
return { signal: controller.signal, dispose: disposeInputs };
|
|
92
|
+
}
|
|
93
|
+
const handler = () => controller.abort(source.reason);
|
|
94
|
+
source.addEventListener("abort", handler, { once: true });
|
|
95
|
+
listeners.push({ source, handler });
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
signal: controller.signal,
|
|
99
|
+
dispose: () => {
|
|
100
|
+
for (const { source, handler } of listeners) {
|
|
101
|
+
source.removeEventListener("abort", handler);
|
|
102
|
+
}
|
|
103
|
+
listeners.length = 0;
|
|
104
|
+
disposeInputs();
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|