@microlink/mql 0.16.1 → 0.17.1
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/dist/index.js +140 -82
- package/dist/index.umd.js +140 -82
- package/package.json +12 -60
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ function getAugmentedNamespace(n) {
|
|
|
10
10
|
var isInstance = false;
|
|
11
11
|
try {
|
|
12
12
|
isInstance = this instanceof a;
|
|
13
|
-
} catch {}
|
|
13
|
+
} catch (e) {}
|
|
14
14
|
if (isInstance) {
|
|
15
15
|
return Reflect.construct(f, arguments, this.constructor);
|
|
16
16
|
}
|
|
@@ -65,6 +65,10 @@ dist.flattie = flattie;
|
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
Base class for all Ky-specific errors. `HTTPError`, `NetworkError`, `TimeoutError`, and `ForceRetryError` extend this class.
|
|
68
|
+
|
|
69
|
+
You can use `instanceof KyError` to check if an error originated from Ky, or use the `isKyError()` type guard for cross-realm compatibility and TypeScript type narrowing.
|
|
70
|
+
|
|
71
|
+
Note: `SchemaValidationError` is intentionally not considered a Ky error. `KyError` covers failures in Ky's HTTP lifecycle (bad status, timeout, retry), while schema validation errors originate from the user-provided schema, not from Ky itself.
|
|
68
72
|
*/
|
|
69
73
|
class KyError extends Error {
|
|
70
74
|
name = 'KyError';
|
|
@@ -76,7 +80,11 @@ class KyError extends Error {
|
|
|
76
80
|
/**
|
|
77
81
|
Error thrown when the response has a non-2xx status code and `throwHttpErrors` is enabled.
|
|
78
82
|
|
|
79
|
-
The error has a `response` property with the `Response` object, a `request` property with the `Request` object, an `options` property with the normalized options, and a `data` property with the pre-parsed response body.
|
|
83
|
+
The error has a `response` property with the `Response` object, a `request` property with the `Request` object, an `options` property with the normalized options (either passed to `ky` when creating an instance with `ky.create()` or directly when performing the request), and a `data` property with the pre-parsed response body. For JSON responses (based on `Content-Type`), the body is parsed using the `parseJson` option if set, or `JSON.parse` by default. For other content types, it is set as plain text. If the body is empty or parsing fails, `data` will be `undefined`. To avoid hanging or excessive buffering, `error.data` population is bounded by the request timeout and a 10 MiB response body size limit. The `data` property is populated before `beforeError` hooks run, so hooks can access it.
|
|
84
|
+
|
|
85
|
+
The response body is automatically consumed when populating `error.data`, so `error.response.json()` and other body methods will not work. Use `error.data` instead. The `error.response` object is still available for headers, status, etc.
|
|
86
|
+
|
|
87
|
+
Be aware that some types of errors, such as network errors, inherently mean that a response was not received. In that case, the error will be an instance of `NetworkError` instead of `HTTPError` and will not contain a `response` property.
|
|
80
88
|
*/
|
|
81
89
|
class HTTPError extends KyError {
|
|
82
90
|
name = 'HTTPError';
|
|
@@ -97,9 +105,11 @@ class HTTPError extends KyError {
|
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
/**
|
|
100
|
-
Error thrown when a network error occurs during the request (e.g., DNS failure, connection refused, offline).
|
|
108
|
+
Error thrown when a network error occurs during the request (e.g., DNS failure, connection refused, offline). It has a `request` property with the `Request` object. The original error is available via the standard `cause` property.
|
|
109
|
+
|
|
110
|
+
Network errors are automatically retried (for retriable methods).
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
Note: Network errors are detected using runtime-specific heuristics. Unrecognized runtimes may produce errors that are not wrapped in `NetworkError`. Use the `shouldRetry` option to handle such cases.
|
|
103
113
|
*/
|
|
104
114
|
class NetworkError extends KyError {
|
|
105
115
|
name = 'NetworkError';
|
|
@@ -161,7 +171,7 @@ class ForceRetryError extends KyError {
|
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
/**
|
|
164
|
-
|
|
174
|
+
The error thrown when [Standard Schema](https://github.com/standard-schema/standard-schema) validation fails in `.json(schema)`. It has an `issues` property with the validation issues from the schema.
|
|
165
175
|
|
|
166
176
|
This error intentionally does not extend `KyError` because it does not represent a failure in Ky's HTTP lifecycle. The request succeeded; the user's schema rejected the data. As such, it is not matched by `isKyError()`.
|
|
167
177
|
|
|
@@ -192,9 +202,7 @@ class SchemaValidationError extends Error {
|
|
|
192
202
|
}
|
|
193
203
|
|
|
194
204
|
/**
|
|
195
|
-
Error thrown when the request times out.
|
|
196
|
-
|
|
197
|
-
The error has a `request` property with the `Request` object.
|
|
205
|
+
Error thrown when the request times out. It has a `request` property with the `Request` object.
|
|
198
206
|
*/
|
|
199
207
|
class TimeoutError extends KyError {
|
|
200
208
|
name = 'TimeoutError';
|
|
@@ -281,7 +289,7 @@ const api = ky.extend({
|
|
|
281
289
|
async ({request, response}) => {
|
|
282
290
|
// Retry based on response body content
|
|
283
291
|
if (response.status === 200) {
|
|
284
|
-
const data = await response.
|
|
292
|
+
const data = await response.json();
|
|
285
293
|
|
|
286
294
|
// Simple retry with default delay
|
|
287
295
|
if (data.error?.code === 'TEMPORARY_ERROR') {
|
|
@@ -365,12 +373,6 @@ const kyOptionKeys = {
|
|
|
365
373
|
fetch: true,
|
|
366
374
|
context: true,
|
|
367
375
|
};
|
|
368
|
-
// Vendor-specific fetch options that should always be passed to fetch()
|
|
369
|
-
// even if they appear on the Request object due to vendor patching.
|
|
370
|
-
// See: https://github.com/sindresorhus/ky/issues/541
|
|
371
|
-
const vendorSpecificOptions = {
|
|
372
|
-
next: true, // Next.js cache revalidation (revalidate, tags)
|
|
373
|
-
};
|
|
374
376
|
// Standard RequestInit options that should NOT be passed separately to fetch()
|
|
375
377
|
// because they're already applied to the Request object.
|
|
376
378
|
// Note: `dispatcher` and `priority` are NOT included here - they're fetch-only
|
|
@@ -495,7 +497,7 @@ const getReplaceState = (value) => isObject(value) && value[replaceSymbol] === t
|
|
|
495
497
|
value,
|
|
496
498
|
};
|
|
497
499
|
/**
|
|
498
|
-
Wraps a value so that `ky.extend()` will replace the parent value instead of merging with it.
|
|
500
|
+
Wraps a value so that `ky.extend()` will replace the parent value instead of merging with it. Works with hooks, headers, search parameters, context, and any other deep-merged option.
|
|
499
501
|
|
|
500
502
|
By default, `.extend()` deep-merges options with the parent instance: hooks get appended, headers get merged, and search parameters get accumulated. Use `replaceOption` when you want to fully replace a merged property instead.
|
|
501
503
|
|
|
@@ -549,7 +551,13 @@ const isPlainObject = (value) => {
|
|
|
549
551
|
};
|
|
550
552
|
const cloneShallow = (value) => {
|
|
551
553
|
if (value instanceof URLSearchParams) {
|
|
552
|
-
|
|
554
|
+
const copy = new URLSearchParams(value);
|
|
555
|
+
const deleted = value[deletedParametersSymbol];
|
|
556
|
+
if (deleted) {
|
|
557
|
+
// Preserve internal deletion markers so init-hook cloning does not resurrect params removed during option merging.
|
|
558
|
+
copy[deletedParametersSymbol] = new Set(deleted);
|
|
559
|
+
}
|
|
560
|
+
return copy;
|
|
553
561
|
}
|
|
554
562
|
if (value instanceof globalThis.Headers) {
|
|
555
563
|
return new globalThis.Headers(value);
|
|
@@ -760,11 +768,13 @@ const normalizeRetryOptions = (retry = {}) => {
|
|
|
760
768
|
if (retry.methods && !Array.isArray(retry.methods)) {
|
|
761
769
|
throw new Error('retry.methods must be an array');
|
|
762
770
|
}
|
|
763
|
-
retry.methods &&= retry.methods.map(method => method.toLowerCase());
|
|
764
771
|
if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
|
|
765
772
|
throw new Error('retry.statusCodes must be an array');
|
|
766
773
|
}
|
|
767
|
-
const normalizedRetry = Object.fromEntries(Object.entries(
|
|
774
|
+
const normalizedRetry = Object.fromEntries(Object.entries({
|
|
775
|
+
...retry,
|
|
776
|
+
methods: retry.methods?.map(method => method.toLowerCase()),
|
|
777
|
+
}).filter(([, value]) => value !== undefined));
|
|
768
778
|
return {
|
|
769
779
|
...defaultRetryOptions,
|
|
770
780
|
...normalizedRetry,
|
|
@@ -808,20 +818,19 @@ async function delay(ms, { signal }) {
|
|
|
808
818
|
});
|
|
809
819
|
}
|
|
810
820
|
|
|
811
|
-
const findUnknownOptions = (
|
|
821
|
+
const findUnknownOptions = (options) => {
|
|
812
822
|
const unknownOptions = {};
|
|
813
823
|
for (const key in options) {
|
|
814
824
|
// Skip inherited properties
|
|
815
825
|
if (!Object.hasOwn(options, key)) {
|
|
816
826
|
continue;
|
|
817
827
|
}
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
|
|
824
|
-
if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys) && (!(key in request) || key in vendorSpecificOptions)) {
|
|
828
|
+
// Forward every non-standard, non-Ky option to fetch().
|
|
829
|
+
// We intentionally do not check whether the key also exists on `Request`, because some runtimes
|
|
830
|
+
// patch `Request.prototype` with fetch-only extensions. For example, Next.js adds `next`, and the
|
|
831
|
+
// old `key in request` heuristic dropped it unless Ky kept a special-case allowlist.
|
|
832
|
+
// Passing all non-standard keys makes that allowlist unnecessary and preserves future fetch extensions too.
|
|
833
|
+
if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys)) {
|
|
825
834
|
unknownOptions[key] = options[key];
|
|
826
835
|
}
|
|
827
836
|
}
|
|
@@ -849,8 +858,8 @@ const hasSearchParameters = (search) => {
|
|
|
849
858
|
};
|
|
850
859
|
|
|
851
860
|
// Inlined from https://github.com/sindresorhus/is-network-error v1.3.1
|
|
852
|
-
const objectToString = Object.prototype.toString;
|
|
853
|
-
const isError = (value) => objectToString.call(value) === '[object Error]';
|
|
861
|
+
const objectToString$1 = Object.prototype.toString;
|
|
862
|
+
const isError = (value) => objectToString$1.call(value) === '[object Error]';
|
|
854
863
|
const errorMessages = new Set([
|
|
855
864
|
'network error', // Chrome
|
|
856
865
|
'NetworkError when attempting to fetch resource.', // Firefox
|
|
@@ -1022,16 +1031,43 @@ const createTextDecoder = (contentType) => {
|
|
|
1022
1031
|
return new TextDecoder();
|
|
1023
1032
|
};
|
|
1024
1033
|
const invalidSchemaMessage = 'The `schema` argument must follow the Standard Schema specification';
|
|
1034
|
+
const cloneRetryOptions = (retry) => {
|
|
1035
|
+
if (typeof retry !== 'object') {
|
|
1036
|
+
return retry;
|
|
1037
|
+
}
|
|
1038
|
+
// Clone nested arrays too so init hooks can mutate retry config without leaking state across requests.
|
|
1039
|
+
return {
|
|
1040
|
+
...retry,
|
|
1041
|
+
...(retry.methods && { methods: [...retry.methods] }),
|
|
1042
|
+
...(retry.statusCodes && { statusCodes: [...retry.statusCodes] }),
|
|
1043
|
+
...(retry.afterStatusCodes && { afterStatusCodes: [...retry.afterStatusCodes] }),
|
|
1044
|
+
};
|
|
1045
|
+
};
|
|
1046
|
+
const objectToString = Object.prototype.toString;
|
|
1047
|
+
const isRequestInstance = (value) => value instanceof globalThis.Request || objectToString.call(value) === '[object Request]';
|
|
1048
|
+
// Accepted custom responses are treated as full Responses throughout Ky.
|
|
1049
|
+
// If a custom fetch returns one, it must behave like a Response for cloning,
|
|
1050
|
+
// body consumption, `json()` decoration, and any enabled stream features.
|
|
1051
|
+
const isResponseInstance = (value) => value instanceof globalThis.Response || objectToString.call(value) === '[object Response]';
|
|
1052
|
+
const cloneSearchParametersForInitHook = (searchParameters) => {
|
|
1053
|
+
if (Array.isArray(searchParameters)) {
|
|
1054
|
+
return searchParameters.map(parameter => [...parameter]);
|
|
1055
|
+
}
|
|
1056
|
+
return cloneShallow(searchParameters);
|
|
1057
|
+
};
|
|
1025
1058
|
// Shallow-clone mutable option properties so init hook mutations don't leak across requests.
|
|
1026
1059
|
function cloneInitHookOptions(options) {
|
|
1027
|
-
|
|
1060
|
+
const clonedOptions = {
|
|
1028
1061
|
...options,
|
|
1029
1062
|
json: cloneShallow(options.json),
|
|
1030
|
-
retry: cloneShallow(options.retry),
|
|
1031
1063
|
context: cloneShallow(options.context),
|
|
1032
1064
|
headers: cloneShallow(options.headers),
|
|
1033
|
-
searchParams:
|
|
1065
|
+
searchParams: cloneSearchParametersForInitHook(options.searchParams),
|
|
1034
1066
|
};
|
|
1067
|
+
if (options.retry !== undefined) {
|
|
1068
|
+
clonedOptions.retry = cloneRetryOptions(options.retry);
|
|
1069
|
+
}
|
|
1070
|
+
return clonedOptions;
|
|
1035
1071
|
}
|
|
1036
1072
|
const validateJsonWithSchema = async (jsonValue, schema) => {
|
|
1037
1073
|
if ((typeof schema !== 'object'
|
|
@@ -1072,43 +1108,49 @@ class Ky {
|
|
|
1072
1108
|
let response = beforeRequestResponse ?? await ky.#retry(async () => ky.#fetch());
|
|
1073
1109
|
let responseFromHook = beforeRequestResponse !== undefined
|
|
1074
1110
|
|| ky.#consumeReturnedResponseFromBeforeRetryHook();
|
|
1075
|
-
if (!(response instanceof globalThis.Response)) {
|
|
1076
|
-
return response;
|
|
1077
|
-
}
|
|
1078
1111
|
for (;;) {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1112
|
+
// `undefined` means a hook stopped the flow without providing a response.
|
|
1113
|
+
// Non-native Responses still continue through Ky if they pass `isResponseInstance()`.
|
|
1114
|
+
if (response === undefined) {
|
|
1115
|
+
return response;
|
|
1082
1116
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1117
|
+
if (isResponseInstance(response)) {
|
|
1118
|
+
try {
|
|
1119
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1120
|
+
response = await ky.#runAfterResponseHooks(response);
|
|
1086
1121
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1122
|
+
catch (error) {
|
|
1123
|
+
if (!(error instanceof ForceRetryError)) {
|
|
1124
|
+
throw error;
|
|
1125
|
+
}
|
|
1126
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1127
|
+
const retriedResponse = await ky.#retryFromError(error, async () => ky.#fetch());
|
|
1128
|
+
if (retriedResponse === undefined) {
|
|
1129
|
+
return retriedResponse;
|
|
1130
|
+
}
|
|
1131
|
+
response = retriedResponse;
|
|
1132
|
+
responseFromHook = ky.#consumeReturnedResponseFromBeforeRetryHook();
|
|
1133
|
+
continue;
|
|
1091
1134
|
}
|
|
1092
|
-
response = retriedResponse;
|
|
1093
|
-
responseFromHook = ky.#consumeReturnedResponseFromBeforeRetryHook();
|
|
1094
|
-
continue;
|
|
1095
1135
|
}
|
|
1136
|
+
const currentResponse = response;
|
|
1096
1137
|
// Opaque responses (`response.type === 'opaque'`) from `no-cors` requests always have `status: 0` and `ok: false`, but this is not a failure - the actual status is hidden by the browser.
|
|
1097
|
-
if (!
|
|
1098
|
-
? ky.#options.throwHttpErrors(
|
|
1138
|
+
if (!currentResponse.ok && currentResponse.type !== 'opaque' && (typeof ky.#options.throwHttpErrors === 'function'
|
|
1139
|
+
? ky.#options.throwHttpErrors(currentResponse.status)
|
|
1099
1140
|
: ky.#options.throwHttpErrors)) {
|
|
1100
1141
|
// `request` must reflect the request that actually failed, but `options` stays as Ky's
|
|
1101
1142
|
// normalized options snapshot. Replacement `Request` instances do not preserve the
|
|
1102
1143
|
// original `BodyInit`, so trying to make `options` mirror arbitrary requests would be lossy.
|
|
1103
|
-
const
|
|
1144
|
+
const httpError = new HTTPError(currentResponse, ky.#getResponseRequest(currentResponse), ky.#getNormalizedOptions());
|
|
1145
|
+
const errorToThrow = httpError;
|
|
1104
1146
|
// eslint-disable-next-line no-await-in-loop
|
|
1105
|
-
|
|
1147
|
+
httpError.data = await ky.#getResponseData(currentResponse);
|
|
1106
1148
|
if (responseFromHook) {
|
|
1107
|
-
throw
|
|
1149
|
+
throw errorToThrow;
|
|
1108
1150
|
}
|
|
1109
1151
|
// eslint-disable-next-line no-await-in-loop
|
|
1110
|
-
const retriedResponse = await ky.#retryFromError(
|
|
1111
|
-
if (
|
|
1152
|
+
const retriedResponse = await ky.#retryFromError(httpError, async () => ky.#fetch());
|
|
1153
|
+
if (retriedResponse === undefined) {
|
|
1112
1154
|
return retriedResponse;
|
|
1113
1155
|
}
|
|
1114
1156
|
response = retriedResponse;
|
|
@@ -1117,6 +1159,9 @@ class Ky {
|
|
|
1117
1159
|
}
|
|
1118
1160
|
break;
|
|
1119
1161
|
}
|
|
1162
|
+
if (!isResponseInstance(response)) {
|
|
1163
|
+
return response;
|
|
1164
|
+
}
|
|
1120
1165
|
ky.#decorateResponse(response);
|
|
1121
1166
|
// If `onDownloadProgress` is passed, it uses the stream API internally
|
|
1122
1167
|
if (ky.#options.onDownloadProgress) {
|
|
@@ -1286,6 +1331,13 @@ class Ky {
|
|
|
1286
1331
|
this.request = new globalThis.Request(this.#input, this.#options);
|
|
1287
1332
|
if (hasSearchParameters(this.#options.searchParams)) {
|
|
1288
1333
|
const url = new URL(this.request.url);
|
|
1334
|
+
const deleted = this.#options.searchParams?.[deletedParametersSymbol];
|
|
1335
|
+
if (deleted) {
|
|
1336
|
+
// Remove keys from the input URL first so later searchParams entries can intentionally re-add them.
|
|
1337
|
+
for (const key of deleted) {
|
|
1338
|
+
url.searchParams.delete(key);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1289
1341
|
if (typeof this.#options.searchParams === 'string') {
|
|
1290
1342
|
const stringSearchParameters = this.#options.searchParams.replace(/^\?/, '');
|
|
1291
1343
|
if (stringSearchParameters !== '') {
|
|
@@ -1308,18 +1360,14 @@ class Ky {
|
|
|
1308
1360
|
}
|
|
1309
1361
|
}
|
|
1310
1362
|
}
|
|
1311
|
-
const deleted = this.#options.searchParams?.[deletedParametersSymbol];
|
|
1312
|
-
if (deleted) {
|
|
1313
|
-
for (const key of deleted) {
|
|
1314
|
-
url.searchParams.delete(key);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
1363
|
// Recreate request with the updated URL. We already have all options in this.#options, including duplex.
|
|
1318
1364
|
this.request = new globalThis.Request(url, this.#options);
|
|
1319
1365
|
}
|
|
1320
1366
|
if (this.#options.onUploadProgress && typeof this.#options.onUploadProgress !== 'function') {
|
|
1321
1367
|
throw new TypeError('The `onUploadProgress` option must be a function');
|
|
1322
1368
|
}
|
|
1369
|
+
// `totalTimeout` starts when the request pipeline is created, so it also includes
|
|
1370
|
+
// Ky's internal scheduling and user hook time before the first fetch attempt.
|
|
1323
1371
|
this.#startTime = typeof this.#options.totalTimeout === 'number' ? this.#getCurrentTime() : undefined;
|
|
1324
1372
|
}
|
|
1325
1373
|
#calculateDelay() {
|
|
@@ -1564,52 +1612,57 @@ class Ky {
|
|
|
1564
1612
|
options: this.#getNormalizedOptions(),
|
|
1565
1613
|
retryCount: 0,
|
|
1566
1614
|
});
|
|
1567
|
-
if (result
|
|
1568
|
-
return result;
|
|
1569
|
-
}
|
|
1570
|
-
if (result instanceof globalThis.Request) {
|
|
1615
|
+
if (isRequestInstance(result)) {
|
|
1571
1616
|
this.#assignRequest(result);
|
|
1572
1617
|
}
|
|
1618
|
+
else if (isResponseInstance(result)) {
|
|
1619
|
+
return result;
|
|
1620
|
+
}
|
|
1573
1621
|
}
|
|
1574
1622
|
return undefined;
|
|
1575
1623
|
}
|
|
1576
1624
|
async #runAfterResponseHooks(response) {
|
|
1577
1625
|
const responseRequest = this.#getResponseRequest(response);
|
|
1578
1626
|
for (const hook of this.#options.hooks.afterResponse) {
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
this.#decorateResponse(clonedResponse);
|
|
1627
|
+
const hookResponse = this.#setResponseRequest(response.clone(), responseRequest);
|
|
1628
|
+
this.#decorateResponse(hookResponse);
|
|
1582
1629
|
let modifiedResponse;
|
|
1583
1630
|
try {
|
|
1584
1631
|
// eslint-disable-next-line no-await-in-loop
|
|
1585
1632
|
modifiedResponse = await hook({
|
|
1586
1633
|
request: this.request,
|
|
1587
1634
|
options: this.#getNormalizedOptions(),
|
|
1588
|
-
response:
|
|
1635
|
+
response: hookResponse,
|
|
1589
1636
|
retryCount: this.#retryCount,
|
|
1590
1637
|
});
|
|
1591
1638
|
}
|
|
1592
1639
|
catch (error) {
|
|
1593
1640
|
// Cancel both responses to prevent memory leaks when hook throws
|
|
1594
|
-
|
|
1641
|
+
if (hookResponse !== response) {
|
|
1642
|
+
this.#cancelResponseBody(hookResponse);
|
|
1643
|
+
}
|
|
1595
1644
|
this.#cancelResponseBody(response);
|
|
1596
1645
|
throw error;
|
|
1597
1646
|
}
|
|
1598
1647
|
if (modifiedResponse instanceof RetryMarker) {
|
|
1599
1648
|
// Cancel both the cloned response passed to the hook and the current response to prevent resource leaks (especially important in Deno/Bun).
|
|
1600
1649
|
// Do not await cancellation since hooks can clone the response, leaving extra tee branches that keep cancel promises pending per the Streams spec.
|
|
1601
|
-
|
|
1650
|
+
if (hookResponse !== response) {
|
|
1651
|
+
this.#cancelResponseBody(hookResponse);
|
|
1652
|
+
}
|
|
1602
1653
|
this.#cancelResponseBody(response);
|
|
1603
1654
|
throw new ForceRetryError(modifiedResponse.options);
|
|
1604
1655
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1656
|
+
const nextResponse = isResponseInstance(modifiedResponse)
|
|
1657
|
+
? this.#setResponseRequest(modifiedResponse, responseRequest)
|
|
1658
|
+
: response;
|
|
1607
1659
|
// Cancel any response bodies we won't use to prevent memory leaks.
|
|
1608
1660
|
// Uses fire-and-forget since hooks may have cloned the response, creating tee branches that block cancellation.
|
|
1609
|
-
|
|
1610
|
-
|
|
1661
|
+
// If the hook wrapped an existing body into a new Response, both Response objects can still point at the same stream.
|
|
1662
|
+
if (hookResponse !== response && hookResponse !== nextResponse && hookResponse.body !== nextResponse.body) {
|
|
1663
|
+
this.#cancelResponseBody(hookResponse);
|
|
1611
1664
|
}
|
|
1612
|
-
if (response !== nextResponse) {
|
|
1665
|
+
if (response !== nextResponse && response.body !== nextResponse.body) {
|
|
1613
1666
|
this.#cancelResponseBody(response);
|
|
1614
1667
|
}
|
|
1615
1668
|
response = nextResponse;
|
|
@@ -1645,7 +1698,11 @@ class Ky {
|
|
|
1645
1698
|
// Apply custom request from forced retry before beforeRetry hooks
|
|
1646
1699
|
// Ensure the custom request has the correct managed signal for timeouts and user aborts
|
|
1647
1700
|
if (error instanceof ForceRetryError && error.customRequest) {
|
|
1648
|
-
|
|
1701
|
+
const customRequest = new globalThis.Request(error.customRequest, this.#options.signal ? { signal: this.#options.signal } : undefined);
|
|
1702
|
+
// Replacement Requests are authoritative by design. Do not rewrite headers here,
|
|
1703
|
+
// even for cross-origin retries. Callers using `ky.retry({request})` explicitly
|
|
1704
|
+
// opted into the exact Request they constructed.
|
|
1705
|
+
this.#assignRequest(customRequest);
|
|
1649
1706
|
}
|
|
1650
1707
|
for (const hook of this.#options.hooks.beforeRetry) {
|
|
1651
1708
|
let hookResult;
|
|
@@ -1665,12 +1722,13 @@ class Ky {
|
|
|
1665
1722
|
}
|
|
1666
1723
|
throw hookError;
|
|
1667
1724
|
}
|
|
1668
|
-
if (hookResult
|
|
1725
|
+
if (isRequestInstance(hookResult)) {
|
|
1726
|
+
// Same contract as `ky.retry({request})`: a Request returned from `beforeRetry`
|
|
1727
|
+
// is used as-is rather than being sanitized or otherwise rewritten by Ky.
|
|
1669
1728
|
this.#assignRequest(hookResult);
|
|
1670
1729
|
break;
|
|
1671
1730
|
}
|
|
1672
|
-
|
|
1673
|
-
if (hookResult instanceof globalThis.Response) {
|
|
1731
|
+
if (isResponseInstance(hookResult)) {
|
|
1674
1732
|
this.#returnedResponseFromBeforeRetryHook = true;
|
|
1675
1733
|
this.#retryCount++;
|
|
1676
1734
|
return hookResult;
|
|
@@ -1697,7 +1755,7 @@ class Ky {
|
|
|
1697
1755
|
// Recreate request with new signal
|
|
1698
1756
|
this.request = new globalThis.Request(this.request, { signal: this.#options.signal });
|
|
1699
1757
|
}
|
|
1700
|
-
const nonRequestOptions = findUnknownOptions(this
|
|
1758
|
+
const nonRequestOptions = findUnknownOptions(this.#options);
|
|
1701
1759
|
const retryRequest = this.#options.retry.limit > 0 ? this.request.clone() : undefined;
|
|
1702
1760
|
const request = this.#wrapRequestWithUploadProgress(this.request, this.#options.body ?? undefined);
|
|
1703
1761
|
// Cloning is done here to prepare in advance for retries.
|
|
@@ -1811,7 +1869,7 @@ var distribution = /*#__PURE__*/Object.freeze({
|
|
|
1811
1869
|
|
|
1812
1870
|
var require$$1 = /*@__PURE__*/getAugmentedNamespace(distribution);
|
|
1813
1871
|
|
|
1814
|
-
const VERSION = '0.
|
|
1872
|
+
const VERSION = '0.17.1';
|
|
1815
1873
|
|
|
1816
1874
|
var constants = {
|
|
1817
1875
|
VERSION,
|
package/dist/index.umd.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
var isInstance = false;
|
|
17
17
|
try {
|
|
18
18
|
isInstance = this instanceof a;
|
|
19
|
-
} catch {}
|
|
19
|
+
} catch (e) {}
|
|
20
20
|
if (isInstance) {
|
|
21
21
|
return Reflect.construct(f, arguments, this.constructor);
|
|
22
22
|
}
|
|
@@ -71,6 +71,10 @@
|
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
Base class for all Ky-specific errors. `HTTPError`, `NetworkError`, `TimeoutError`, and `ForceRetryError` extend this class.
|
|
74
|
+
|
|
75
|
+
You can use `instanceof KyError` to check if an error originated from Ky, or use the `isKyError()` type guard for cross-realm compatibility and TypeScript type narrowing.
|
|
76
|
+
|
|
77
|
+
Note: `SchemaValidationError` is intentionally not considered a Ky error. `KyError` covers failures in Ky's HTTP lifecycle (bad status, timeout, retry), while schema validation errors originate from the user-provided schema, not from Ky itself.
|
|
74
78
|
*/
|
|
75
79
|
class KyError extends Error {
|
|
76
80
|
name = 'KyError';
|
|
@@ -82,7 +86,11 @@
|
|
|
82
86
|
/**
|
|
83
87
|
Error thrown when the response has a non-2xx status code and `throwHttpErrors` is enabled.
|
|
84
88
|
|
|
85
|
-
The error has a `response` property with the `Response` object, a `request` property with the `Request` object, an `options` property with the normalized options, and a `data` property with the pre-parsed response body.
|
|
89
|
+
The error has a `response` property with the `Response` object, a `request` property with the `Request` object, an `options` property with the normalized options (either passed to `ky` when creating an instance with `ky.create()` or directly when performing the request), and a `data` property with the pre-parsed response body. For JSON responses (based on `Content-Type`), the body is parsed using the `parseJson` option if set, or `JSON.parse` by default. For other content types, it is set as plain text. If the body is empty or parsing fails, `data` will be `undefined`. To avoid hanging or excessive buffering, `error.data` population is bounded by the request timeout and a 10 MiB response body size limit. The `data` property is populated before `beforeError` hooks run, so hooks can access it.
|
|
90
|
+
|
|
91
|
+
The response body is automatically consumed when populating `error.data`, so `error.response.json()` and other body methods will not work. Use `error.data` instead. The `error.response` object is still available for headers, status, etc.
|
|
92
|
+
|
|
93
|
+
Be aware that some types of errors, such as network errors, inherently mean that a response was not received. In that case, the error will be an instance of `NetworkError` instead of `HTTPError` and will not contain a `response` property.
|
|
86
94
|
*/
|
|
87
95
|
class HTTPError extends KyError {
|
|
88
96
|
name = 'HTTPError';
|
|
@@ -103,9 +111,11 @@
|
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
/**
|
|
106
|
-
Error thrown when a network error occurs during the request (e.g., DNS failure, connection refused, offline).
|
|
114
|
+
Error thrown when a network error occurs during the request (e.g., DNS failure, connection refused, offline). It has a `request` property with the `Request` object. The original error is available via the standard `cause` property.
|
|
115
|
+
|
|
116
|
+
Network errors are automatically retried (for retriable methods).
|
|
107
117
|
|
|
108
|
-
|
|
118
|
+
Note: Network errors are detected using runtime-specific heuristics. Unrecognized runtimes may produce errors that are not wrapped in `NetworkError`. Use the `shouldRetry` option to handle such cases.
|
|
109
119
|
*/
|
|
110
120
|
class NetworkError extends KyError {
|
|
111
121
|
name = 'NetworkError';
|
|
@@ -167,7 +177,7 @@
|
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
/**
|
|
170
|
-
|
|
180
|
+
The error thrown when [Standard Schema](https://github.com/standard-schema/standard-schema) validation fails in `.json(schema)`. It has an `issues` property with the validation issues from the schema.
|
|
171
181
|
|
|
172
182
|
This error intentionally does not extend `KyError` because it does not represent a failure in Ky's HTTP lifecycle. The request succeeded; the user's schema rejected the data. As such, it is not matched by `isKyError()`.
|
|
173
183
|
|
|
@@ -198,9 +208,7 @@
|
|
|
198
208
|
}
|
|
199
209
|
|
|
200
210
|
/**
|
|
201
|
-
Error thrown when the request times out.
|
|
202
|
-
|
|
203
|
-
The error has a `request` property with the `Request` object.
|
|
211
|
+
Error thrown when the request times out. It has a `request` property with the `Request` object.
|
|
204
212
|
*/
|
|
205
213
|
class TimeoutError extends KyError {
|
|
206
214
|
name = 'TimeoutError';
|
|
@@ -287,7 +295,7 @@
|
|
|
287
295
|
async ({request, response}) => {
|
|
288
296
|
// Retry based on response body content
|
|
289
297
|
if (response.status === 200) {
|
|
290
|
-
const data = await response.
|
|
298
|
+
const data = await response.json();
|
|
291
299
|
|
|
292
300
|
// Simple retry with default delay
|
|
293
301
|
if (data.error?.code === 'TEMPORARY_ERROR') {
|
|
@@ -371,12 +379,6 @@
|
|
|
371
379
|
fetch: true,
|
|
372
380
|
context: true,
|
|
373
381
|
};
|
|
374
|
-
// Vendor-specific fetch options that should always be passed to fetch()
|
|
375
|
-
// even if they appear on the Request object due to vendor patching.
|
|
376
|
-
// See: https://github.com/sindresorhus/ky/issues/541
|
|
377
|
-
const vendorSpecificOptions = {
|
|
378
|
-
next: true, // Next.js cache revalidation (revalidate, tags)
|
|
379
|
-
};
|
|
380
382
|
// Standard RequestInit options that should NOT be passed separately to fetch()
|
|
381
383
|
// because they're already applied to the Request object.
|
|
382
384
|
// Note: `dispatcher` and `priority` are NOT included here - they're fetch-only
|
|
@@ -501,7 +503,7 @@
|
|
|
501
503
|
value,
|
|
502
504
|
};
|
|
503
505
|
/**
|
|
504
|
-
Wraps a value so that `ky.extend()` will replace the parent value instead of merging with it.
|
|
506
|
+
Wraps a value so that `ky.extend()` will replace the parent value instead of merging with it. Works with hooks, headers, search parameters, context, and any other deep-merged option.
|
|
505
507
|
|
|
506
508
|
By default, `.extend()` deep-merges options with the parent instance: hooks get appended, headers get merged, and search parameters get accumulated. Use `replaceOption` when you want to fully replace a merged property instead.
|
|
507
509
|
|
|
@@ -555,7 +557,13 @@
|
|
|
555
557
|
};
|
|
556
558
|
const cloneShallow = (value) => {
|
|
557
559
|
if (value instanceof URLSearchParams) {
|
|
558
|
-
|
|
560
|
+
const copy = new URLSearchParams(value);
|
|
561
|
+
const deleted = value[deletedParametersSymbol];
|
|
562
|
+
if (deleted) {
|
|
563
|
+
// Preserve internal deletion markers so init-hook cloning does not resurrect params removed during option merging.
|
|
564
|
+
copy[deletedParametersSymbol] = new Set(deleted);
|
|
565
|
+
}
|
|
566
|
+
return copy;
|
|
559
567
|
}
|
|
560
568
|
if (value instanceof globalThis.Headers) {
|
|
561
569
|
return new globalThis.Headers(value);
|
|
@@ -766,11 +774,13 @@
|
|
|
766
774
|
if (retry.methods && !Array.isArray(retry.methods)) {
|
|
767
775
|
throw new Error('retry.methods must be an array');
|
|
768
776
|
}
|
|
769
|
-
retry.methods &&= retry.methods.map(method => method.toLowerCase());
|
|
770
777
|
if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
|
|
771
778
|
throw new Error('retry.statusCodes must be an array');
|
|
772
779
|
}
|
|
773
|
-
const normalizedRetry = Object.fromEntries(Object.entries(
|
|
780
|
+
const normalizedRetry = Object.fromEntries(Object.entries({
|
|
781
|
+
...retry,
|
|
782
|
+
methods: retry.methods?.map(method => method.toLowerCase()),
|
|
783
|
+
}).filter(([, value]) => value !== undefined));
|
|
774
784
|
return {
|
|
775
785
|
...defaultRetryOptions,
|
|
776
786
|
...normalizedRetry,
|
|
@@ -814,20 +824,19 @@
|
|
|
814
824
|
});
|
|
815
825
|
}
|
|
816
826
|
|
|
817
|
-
const findUnknownOptions = (
|
|
827
|
+
const findUnknownOptions = (options) => {
|
|
818
828
|
const unknownOptions = {};
|
|
819
829
|
for (const key in options) {
|
|
820
830
|
// Skip inherited properties
|
|
821
831
|
if (!Object.hasOwn(options, key)) {
|
|
822
832
|
continue;
|
|
823
833
|
}
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
|
|
830
|
-
if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys) && (!(key in request) || key in vendorSpecificOptions)) {
|
|
834
|
+
// Forward every non-standard, non-Ky option to fetch().
|
|
835
|
+
// We intentionally do not check whether the key also exists on `Request`, because some runtimes
|
|
836
|
+
// patch `Request.prototype` with fetch-only extensions. For example, Next.js adds `next`, and the
|
|
837
|
+
// old `key in request` heuristic dropped it unless Ky kept a special-case allowlist.
|
|
838
|
+
// Passing all non-standard keys makes that allowlist unnecessary and preserves future fetch extensions too.
|
|
839
|
+
if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys)) {
|
|
831
840
|
unknownOptions[key] = options[key];
|
|
832
841
|
}
|
|
833
842
|
}
|
|
@@ -855,8 +864,8 @@
|
|
|
855
864
|
};
|
|
856
865
|
|
|
857
866
|
// Inlined from https://github.com/sindresorhus/is-network-error v1.3.1
|
|
858
|
-
const objectToString = Object.prototype.toString;
|
|
859
|
-
const isError = (value) => objectToString.call(value) === '[object Error]';
|
|
867
|
+
const objectToString$1 = Object.prototype.toString;
|
|
868
|
+
const isError = (value) => objectToString$1.call(value) === '[object Error]';
|
|
860
869
|
const errorMessages = new Set([
|
|
861
870
|
'network error', // Chrome
|
|
862
871
|
'NetworkError when attempting to fetch resource.', // Firefox
|
|
@@ -1028,16 +1037,43 @@
|
|
|
1028
1037
|
return new TextDecoder();
|
|
1029
1038
|
};
|
|
1030
1039
|
const invalidSchemaMessage = 'The `schema` argument must follow the Standard Schema specification';
|
|
1040
|
+
const cloneRetryOptions = (retry) => {
|
|
1041
|
+
if (typeof retry !== 'object') {
|
|
1042
|
+
return retry;
|
|
1043
|
+
}
|
|
1044
|
+
// Clone nested arrays too so init hooks can mutate retry config without leaking state across requests.
|
|
1045
|
+
return {
|
|
1046
|
+
...retry,
|
|
1047
|
+
...(retry.methods && { methods: [...retry.methods] }),
|
|
1048
|
+
...(retry.statusCodes && { statusCodes: [...retry.statusCodes] }),
|
|
1049
|
+
...(retry.afterStatusCodes && { afterStatusCodes: [...retry.afterStatusCodes] }),
|
|
1050
|
+
};
|
|
1051
|
+
};
|
|
1052
|
+
const objectToString = Object.prototype.toString;
|
|
1053
|
+
const isRequestInstance = (value) => value instanceof globalThis.Request || objectToString.call(value) === '[object Request]';
|
|
1054
|
+
// Accepted custom responses are treated as full Responses throughout Ky.
|
|
1055
|
+
// If a custom fetch returns one, it must behave like a Response for cloning,
|
|
1056
|
+
// body consumption, `json()` decoration, and any enabled stream features.
|
|
1057
|
+
const isResponseInstance = (value) => value instanceof globalThis.Response || objectToString.call(value) === '[object Response]';
|
|
1058
|
+
const cloneSearchParametersForInitHook = (searchParameters) => {
|
|
1059
|
+
if (Array.isArray(searchParameters)) {
|
|
1060
|
+
return searchParameters.map(parameter => [...parameter]);
|
|
1061
|
+
}
|
|
1062
|
+
return cloneShallow(searchParameters);
|
|
1063
|
+
};
|
|
1031
1064
|
// Shallow-clone mutable option properties so init hook mutations don't leak across requests.
|
|
1032
1065
|
function cloneInitHookOptions(options) {
|
|
1033
|
-
|
|
1066
|
+
const clonedOptions = {
|
|
1034
1067
|
...options,
|
|
1035
1068
|
json: cloneShallow(options.json),
|
|
1036
|
-
retry: cloneShallow(options.retry),
|
|
1037
1069
|
context: cloneShallow(options.context),
|
|
1038
1070
|
headers: cloneShallow(options.headers),
|
|
1039
|
-
searchParams:
|
|
1071
|
+
searchParams: cloneSearchParametersForInitHook(options.searchParams),
|
|
1040
1072
|
};
|
|
1073
|
+
if (options.retry !== undefined) {
|
|
1074
|
+
clonedOptions.retry = cloneRetryOptions(options.retry);
|
|
1075
|
+
}
|
|
1076
|
+
return clonedOptions;
|
|
1041
1077
|
}
|
|
1042
1078
|
const validateJsonWithSchema = async (jsonValue, schema) => {
|
|
1043
1079
|
if ((typeof schema !== 'object'
|
|
@@ -1078,43 +1114,49 @@
|
|
|
1078
1114
|
let response = beforeRequestResponse ?? await ky.#retry(async () => ky.#fetch());
|
|
1079
1115
|
let responseFromHook = beforeRequestResponse !== undefined
|
|
1080
1116
|
|| ky.#consumeReturnedResponseFromBeforeRetryHook();
|
|
1081
|
-
if (!(response instanceof globalThis.Response)) {
|
|
1082
|
-
return response;
|
|
1083
|
-
}
|
|
1084
1117
|
for (;;) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1118
|
+
// `undefined` means a hook stopped the flow without providing a response.
|
|
1119
|
+
// Non-native Responses still continue through Ky if they pass `isResponseInstance()`.
|
|
1120
|
+
if (response === undefined) {
|
|
1121
|
+
return response;
|
|
1088
1122
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1123
|
+
if (isResponseInstance(response)) {
|
|
1124
|
+
try {
|
|
1125
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1126
|
+
response = await ky.#runAfterResponseHooks(response);
|
|
1092
1127
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1128
|
+
catch (error) {
|
|
1129
|
+
if (!(error instanceof ForceRetryError)) {
|
|
1130
|
+
throw error;
|
|
1131
|
+
}
|
|
1132
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1133
|
+
const retriedResponse = await ky.#retryFromError(error, async () => ky.#fetch());
|
|
1134
|
+
if (retriedResponse === undefined) {
|
|
1135
|
+
return retriedResponse;
|
|
1136
|
+
}
|
|
1137
|
+
response = retriedResponse;
|
|
1138
|
+
responseFromHook = ky.#consumeReturnedResponseFromBeforeRetryHook();
|
|
1139
|
+
continue;
|
|
1097
1140
|
}
|
|
1098
|
-
response = retriedResponse;
|
|
1099
|
-
responseFromHook = ky.#consumeReturnedResponseFromBeforeRetryHook();
|
|
1100
|
-
continue;
|
|
1101
1141
|
}
|
|
1142
|
+
const currentResponse = response;
|
|
1102
1143
|
// Opaque responses (`response.type === 'opaque'`) from `no-cors` requests always have `status: 0` and `ok: false`, but this is not a failure - the actual status is hidden by the browser.
|
|
1103
|
-
if (!
|
|
1104
|
-
? ky.#options.throwHttpErrors(
|
|
1144
|
+
if (!currentResponse.ok && currentResponse.type !== 'opaque' && (typeof ky.#options.throwHttpErrors === 'function'
|
|
1145
|
+
? ky.#options.throwHttpErrors(currentResponse.status)
|
|
1105
1146
|
: ky.#options.throwHttpErrors)) {
|
|
1106
1147
|
// `request` must reflect the request that actually failed, but `options` stays as Ky's
|
|
1107
1148
|
// normalized options snapshot. Replacement `Request` instances do not preserve the
|
|
1108
1149
|
// original `BodyInit`, so trying to make `options` mirror arbitrary requests would be lossy.
|
|
1109
|
-
const
|
|
1150
|
+
const httpError = new HTTPError(currentResponse, ky.#getResponseRequest(currentResponse), ky.#getNormalizedOptions());
|
|
1151
|
+
const errorToThrow = httpError;
|
|
1110
1152
|
// eslint-disable-next-line no-await-in-loop
|
|
1111
|
-
|
|
1153
|
+
httpError.data = await ky.#getResponseData(currentResponse);
|
|
1112
1154
|
if (responseFromHook) {
|
|
1113
|
-
throw
|
|
1155
|
+
throw errorToThrow;
|
|
1114
1156
|
}
|
|
1115
1157
|
// eslint-disable-next-line no-await-in-loop
|
|
1116
|
-
const retriedResponse = await ky.#retryFromError(
|
|
1117
|
-
if (
|
|
1158
|
+
const retriedResponse = await ky.#retryFromError(httpError, async () => ky.#fetch());
|
|
1159
|
+
if (retriedResponse === undefined) {
|
|
1118
1160
|
return retriedResponse;
|
|
1119
1161
|
}
|
|
1120
1162
|
response = retriedResponse;
|
|
@@ -1123,6 +1165,9 @@
|
|
|
1123
1165
|
}
|
|
1124
1166
|
break;
|
|
1125
1167
|
}
|
|
1168
|
+
if (!isResponseInstance(response)) {
|
|
1169
|
+
return response;
|
|
1170
|
+
}
|
|
1126
1171
|
ky.#decorateResponse(response);
|
|
1127
1172
|
// If `onDownloadProgress` is passed, it uses the stream API internally
|
|
1128
1173
|
if (ky.#options.onDownloadProgress) {
|
|
@@ -1292,6 +1337,13 @@
|
|
|
1292
1337
|
this.request = new globalThis.Request(this.#input, this.#options);
|
|
1293
1338
|
if (hasSearchParameters(this.#options.searchParams)) {
|
|
1294
1339
|
const url = new URL(this.request.url);
|
|
1340
|
+
const deleted = this.#options.searchParams?.[deletedParametersSymbol];
|
|
1341
|
+
if (deleted) {
|
|
1342
|
+
// Remove keys from the input URL first so later searchParams entries can intentionally re-add them.
|
|
1343
|
+
for (const key of deleted) {
|
|
1344
|
+
url.searchParams.delete(key);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1295
1347
|
if (typeof this.#options.searchParams === 'string') {
|
|
1296
1348
|
const stringSearchParameters = this.#options.searchParams.replace(/^\?/, '');
|
|
1297
1349
|
if (stringSearchParameters !== '') {
|
|
@@ -1314,18 +1366,14 @@
|
|
|
1314
1366
|
}
|
|
1315
1367
|
}
|
|
1316
1368
|
}
|
|
1317
|
-
const deleted = this.#options.searchParams?.[deletedParametersSymbol];
|
|
1318
|
-
if (deleted) {
|
|
1319
|
-
for (const key of deleted) {
|
|
1320
|
-
url.searchParams.delete(key);
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
1369
|
// Recreate request with the updated URL. We already have all options in this.#options, including duplex.
|
|
1324
1370
|
this.request = new globalThis.Request(url, this.#options);
|
|
1325
1371
|
}
|
|
1326
1372
|
if (this.#options.onUploadProgress && typeof this.#options.onUploadProgress !== 'function') {
|
|
1327
1373
|
throw new TypeError('The `onUploadProgress` option must be a function');
|
|
1328
1374
|
}
|
|
1375
|
+
// `totalTimeout` starts when the request pipeline is created, so it also includes
|
|
1376
|
+
// Ky's internal scheduling and user hook time before the first fetch attempt.
|
|
1329
1377
|
this.#startTime = typeof this.#options.totalTimeout === 'number' ? this.#getCurrentTime() : undefined;
|
|
1330
1378
|
}
|
|
1331
1379
|
#calculateDelay() {
|
|
@@ -1570,52 +1618,57 @@
|
|
|
1570
1618
|
options: this.#getNormalizedOptions(),
|
|
1571
1619
|
retryCount: 0,
|
|
1572
1620
|
});
|
|
1573
|
-
if (result
|
|
1574
|
-
return result;
|
|
1575
|
-
}
|
|
1576
|
-
if (result instanceof globalThis.Request) {
|
|
1621
|
+
if (isRequestInstance(result)) {
|
|
1577
1622
|
this.#assignRequest(result);
|
|
1578
1623
|
}
|
|
1624
|
+
else if (isResponseInstance(result)) {
|
|
1625
|
+
return result;
|
|
1626
|
+
}
|
|
1579
1627
|
}
|
|
1580
1628
|
return undefined;
|
|
1581
1629
|
}
|
|
1582
1630
|
async #runAfterResponseHooks(response) {
|
|
1583
1631
|
const responseRequest = this.#getResponseRequest(response);
|
|
1584
1632
|
for (const hook of this.#options.hooks.afterResponse) {
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
this.#decorateResponse(clonedResponse);
|
|
1633
|
+
const hookResponse = this.#setResponseRequest(response.clone(), responseRequest);
|
|
1634
|
+
this.#decorateResponse(hookResponse);
|
|
1588
1635
|
let modifiedResponse;
|
|
1589
1636
|
try {
|
|
1590
1637
|
// eslint-disable-next-line no-await-in-loop
|
|
1591
1638
|
modifiedResponse = await hook({
|
|
1592
1639
|
request: this.request,
|
|
1593
1640
|
options: this.#getNormalizedOptions(),
|
|
1594
|
-
response:
|
|
1641
|
+
response: hookResponse,
|
|
1595
1642
|
retryCount: this.#retryCount,
|
|
1596
1643
|
});
|
|
1597
1644
|
}
|
|
1598
1645
|
catch (error) {
|
|
1599
1646
|
// Cancel both responses to prevent memory leaks when hook throws
|
|
1600
|
-
|
|
1647
|
+
if (hookResponse !== response) {
|
|
1648
|
+
this.#cancelResponseBody(hookResponse);
|
|
1649
|
+
}
|
|
1601
1650
|
this.#cancelResponseBody(response);
|
|
1602
1651
|
throw error;
|
|
1603
1652
|
}
|
|
1604
1653
|
if (modifiedResponse instanceof RetryMarker) {
|
|
1605
1654
|
// Cancel both the cloned response passed to the hook and the current response to prevent resource leaks (especially important in Deno/Bun).
|
|
1606
1655
|
// Do not await cancellation since hooks can clone the response, leaving extra tee branches that keep cancel promises pending per the Streams spec.
|
|
1607
|
-
|
|
1656
|
+
if (hookResponse !== response) {
|
|
1657
|
+
this.#cancelResponseBody(hookResponse);
|
|
1658
|
+
}
|
|
1608
1659
|
this.#cancelResponseBody(response);
|
|
1609
1660
|
throw new ForceRetryError(modifiedResponse.options);
|
|
1610
1661
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1662
|
+
const nextResponse = isResponseInstance(modifiedResponse)
|
|
1663
|
+
? this.#setResponseRequest(modifiedResponse, responseRequest)
|
|
1664
|
+
: response;
|
|
1613
1665
|
// Cancel any response bodies we won't use to prevent memory leaks.
|
|
1614
1666
|
// Uses fire-and-forget since hooks may have cloned the response, creating tee branches that block cancellation.
|
|
1615
|
-
|
|
1616
|
-
|
|
1667
|
+
// If the hook wrapped an existing body into a new Response, both Response objects can still point at the same stream.
|
|
1668
|
+
if (hookResponse !== response && hookResponse !== nextResponse && hookResponse.body !== nextResponse.body) {
|
|
1669
|
+
this.#cancelResponseBody(hookResponse);
|
|
1617
1670
|
}
|
|
1618
|
-
if (response !== nextResponse) {
|
|
1671
|
+
if (response !== nextResponse && response.body !== nextResponse.body) {
|
|
1619
1672
|
this.#cancelResponseBody(response);
|
|
1620
1673
|
}
|
|
1621
1674
|
response = nextResponse;
|
|
@@ -1651,7 +1704,11 @@
|
|
|
1651
1704
|
// Apply custom request from forced retry before beforeRetry hooks
|
|
1652
1705
|
// Ensure the custom request has the correct managed signal for timeouts and user aborts
|
|
1653
1706
|
if (error instanceof ForceRetryError && error.customRequest) {
|
|
1654
|
-
|
|
1707
|
+
const customRequest = new globalThis.Request(error.customRequest, this.#options.signal ? { signal: this.#options.signal } : undefined);
|
|
1708
|
+
// Replacement Requests are authoritative by design. Do not rewrite headers here,
|
|
1709
|
+
// even for cross-origin retries. Callers using `ky.retry({request})` explicitly
|
|
1710
|
+
// opted into the exact Request they constructed.
|
|
1711
|
+
this.#assignRequest(customRequest);
|
|
1655
1712
|
}
|
|
1656
1713
|
for (const hook of this.#options.hooks.beforeRetry) {
|
|
1657
1714
|
let hookResult;
|
|
@@ -1671,12 +1728,13 @@
|
|
|
1671
1728
|
}
|
|
1672
1729
|
throw hookError;
|
|
1673
1730
|
}
|
|
1674
|
-
if (hookResult
|
|
1731
|
+
if (isRequestInstance(hookResult)) {
|
|
1732
|
+
// Same contract as `ky.retry({request})`: a Request returned from `beforeRetry`
|
|
1733
|
+
// is used as-is rather than being sanitized or otherwise rewritten by Ky.
|
|
1675
1734
|
this.#assignRequest(hookResult);
|
|
1676
1735
|
break;
|
|
1677
1736
|
}
|
|
1678
|
-
|
|
1679
|
-
if (hookResult instanceof globalThis.Response) {
|
|
1737
|
+
if (isResponseInstance(hookResult)) {
|
|
1680
1738
|
this.#returnedResponseFromBeforeRetryHook = true;
|
|
1681
1739
|
this.#retryCount++;
|
|
1682
1740
|
return hookResult;
|
|
@@ -1703,7 +1761,7 @@
|
|
|
1703
1761
|
// Recreate request with new signal
|
|
1704
1762
|
this.request = new globalThis.Request(this.request, { signal: this.#options.signal });
|
|
1705
1763
|
}
|
|
1706
|
-
const nonRequestOptions = findUnknownOptions(this
|
|
1764
|
+
const nonRequestOptions = findUnknownOptions(this.#options);
|
|
1707
1765
|
const retryRequest = this.#options.retry.limit > 0 ? this.request.clone() : undefined;
|
|
1708
1766
|
const request = this.#wrapRequestWithUploadProgress(this.request, this.#options.body ?? undefined);
|
|
1709
1767
|
// Cloning is done here to prepare in advance for retries.
|
|
@@ -1817,7 +1875,7 @@
|
|
|
1817
1875
|
|
|
1818
1876
|
var require$$1 = /*@__PURE__*/getAugmentedNamespace(distribution);
|
|
1819
1877
|
|
|
1820
|
-
const VERSION = '0.
|
|
1878
|
+
const VERSION = '0.17.1';
|
|
1821
1879
|
|
|
1822
1880
|
var constants = {
|
|
1823
1881
|
VERSION,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@microlink/mql",
|
|
3
3
|
"description": "Microlink Query Language. The official HTTP client to interact with Microlink API for Node.js, browsers & Deno.",
|
|
4
4
|
"homepage": "https://microlink.io/mql",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.17.1",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
@@ -34,11 +34,12 @@
|
|
|
34
34
|
}
|
|
35
35
|
],
|
|
36
36
|
"repository": {
|
|
37
|
+
"directory": "packages/mql",
|
|
37
38
|
"type": "git",
|
|
38
|
-
"url": "git+https://github.com/microlinkhq/
|
|
39
|
+
"url": "git+https://github.com/microlinkhq/microlink.git"
|
|
39
40
|
},
|
|
40
41
|
"bugs": {
|
|
41
|
-
"url": "https://github.com/microlinkhq/
|
|
42
|
+
"url": "https://github.com/microlinkhq/microlink/issues"
|
|
42
43
|
},
|
|
43
44
|
"keywords": [
|
|
44
45
|
"api",
|
|
@@ -52,26 +53,15 @@
|
|
|
52
53
|
"ky": "~2.0.0"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"@commitlint/cli": "latest",
|
|
56
|
-
"@commitlint/config-conventional": "latest",
|
|
57
56
|
"@rollup/plugin-commonjs": "latest",
|
|
58
57
|
"@rollup/plugin-node-resolve": "latest",
|
|
59
58
|
"@rollup/plugin-replace": "latest",
|
|
60
59
|
"@rollup/plugin-terser": "latest",
|
|
61
60
|
"async-listen": "latest",
|
|
62
61
|
"ava": "7",
|
|
63
|
-
"c8": "latest",
|
|
64
|
-
"ci-publish": "latest",
|
|
65
|
-
"git-authors-cli": "latest",
|
|
66
|
-
"github-generate-release": "latest",
|
|
67
|
-
"nano-staged": "latest",
|
|
68
|
-
"prettier-standard": "latest",
|
|
69
62
|
"rollup": "latest",
|
|
70
63
|
"rollup-plugin-filesize": "latest",
|
|
71
|
-
"
|
|
72
|
-
"standard": "latest",
|
|
73
|
-
"standard-markdown": "latest",
|
|
74
|
-
"standard-version": "latest",
|
|
64
|
+
"tinyspawn": "latest",
|
|
75
65
|
"tsd": "latest"
|
|
76
66
|
},
|
|
77
67
|
"engines": {
|
|
@@ -85,19 +75,12 @@
|
|
|
85
75
|
],
|
|
86
76
|
"scripts": {
|
|
87
77
|
"build": "rollup -c rollup.config.js --bundleConfigAsCjs",
|
|
88
|
-
"clean": "rm -rf node_modules",
|
|
89
78
|
"clean:build": "rm -rf dist/index.js",
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"prepublishOnly": "npm run build",
|
|
96
|
-
"pretest": "npm run lint && npm run build",
|
|
97
|
-
"release": "standard-version -a",
|
|
98
|
-
"release:github": "github-generate-release",
|
|
99
|
-
"release:tags": "git push --follow-tags origin HEAD:master",
|
|
100
|
-
"test": "c8 ava --verbose"
|
|
79
|
+
"dev": "pnpm run build -- -w",
|
|
80
|
+
"prebuild": "pnpm run clean:build",
|
|
81
|
+
"prepublishOnly": "pnpm run build",
|
|
82
|
+
"pretest": "pnpm run build",
|
|
83
|
+
"test": "ava --verbose && tsd"
|
|
101
84
|
},
|
|
102
85
|
"license": "MIT",
|
|
103
86
|
"ava": {
|
|
@@ -107,38 +90,6 @@
|
|
|
107
90
|
],
|
|
108
91
|
"timeout": "1m"
|
|
109
92
|
},
|
|
110
|
-
"commitlint": {
|
|
111
|
-
"extends": [
|
|
112
|
-
"@commitlint/config-conventional"
|
|
113
|
-
],
|
|
114
|
-
"rules": {
|
|
115
|
-
"body-max-line-length": [
|
|
116
|
-
0
|
|
117
|
-
]
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
"nano-staged": {
|
|
121
|
-
"*.js": [
|
|
122
|
-
"npx -y @kikobeats/prettier-standard",
|
|
123
|
-
"standard --fix"
|
|
124
|
-
],
|
|
125
|
-
"*.md": [
|
|
126
|
-
"standard-markdown"
|
|
127
|
-
],
|
|
128
|
-
"package.json": [
|
|
129
|
-
"finepack"
|
|
130
|
-
]
|
|
131
|
-
},
|
|
132
|
-
"simple-git-hooks": {
|
|
133
|
-
"commit-msg": "npx commitlint --edit",
|
|
134
|
-
"pre-commit": "npx nano-staged"
|
|
135
|
-
},
|
|
136
|
-
"standard": {
|
|
137
|
-
"ignore": [
|
|
138
|
-
"dist/index.js",
|
|
139
|
-
"dist/index.umd.js"
|
|
140
|
-
]
|
|
141
|
-
},
|
|
142
93
|
"tsd": {
|
|
143
94
|
"compilerOptions": {
|
|
144
95
|
"baseUrl": ".",
|
|
@@ -149,5 +100,6 @@
|
|
|
149
100
|
}
|
|
150
101
|
},
|
|
151
102
|
"directory": "test"
|
|
152
|
-
}
|
|
103
|
+
},
|
|
104
|
+
"gitHead": "28020344ffd40e3253e8cfa345e2a0bfe15deda9"
|
|
153
105
|
}
|