apify-client 3.0.0-beta.9 → 3.0.0-rc.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/README.md +10 -2
- package/dist/apify_api_error.d.ts +11 -26
- package/dist/apify_api_error.js +31 -22
- package/dist/apify_client.d.ts +125 -10
- package/dist/apify_client.js +133 -48
- package/dist/base/api_client.d.ts +7 -7
- package/dist/base/api_client.js +36 -24
- package/dist/base/resource_client.d.ts +21 -8
- package/dist/base/resource_client.js +67 -33
- package/dist/base/resource_collection_client.d.ts +12 -4
- package/dist/base/resource_collection_client.js +34 -11
- package/dist/body_parser.d.ts +3 -4
- package/dist/body_parser.js +19 -26
- package/dist/bundle.js +27 -22
- package/dist/bundle.js.map +1 -1
- package/dist/generated/api.d.ts +785 -290
- package/dist/generated/schemas.d.ts +720 -747
- package/dist/generated/schemas.js +166 -162
- package/dist/http_clients/axios.d.ts +60 -0
- package/dist/http_clients/axios.js +117 -0
- package/dist/http_clients/base.d.ts +331 -0
- package/dist/http_clients/base.js +563 -0
- package/dist/http_clients/index.d.ts +4 -0
- package/dist/http_clients/index.js +2 -0
- package/dist/http_compressors/base.d.ts +40 -0
- package/dist/http_compressors/base.js +9 -0
- package/dist/http_compressors/brotli.d.ts +33 -0
- package/dist/http_compressors/brotli.js +39 -0
- package/dist/http_compressors/gzip.d.ts +33 -0
- package/dist/http_compressors/gzip.js +39 -0
- package/dist/http_compressors/index.d.ts +6 -0
- package/dist/http_compressors/index.js +2 -0
- package/dist/http_compressors/resolve.d.ts +23 -0
- package/dist/http_compressors/resolve.js +27 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/invalid_response_body_error.d.ts +13 -0
- package/dist/invalid_response_body_error.js +17 -0
- package/dist/models.d.ts +162 -194
- package/dist/models.js +6 -4
- package/dist/resource_clients/actor.d.ts +74 -34
- package/dist/resource_clients/actor.js +87 -56
- package/dist/resource_clients/actor_collection.d.ts +18 -12
- package/dist/resource_clients/actor_collection.js +11 -3
- package/dist/resource_clients/actor_env_var.d.ts +11 -4
- package/dist/resource_clients/actor_env_var.js +16 -6
- package/dist/resource_clients/actor_env_var_collection.d.ts +9 -4
- package/dist/resource_clients/actor_env_var_collection.js +11 -4
- package/dist/resource_clients/actor_version.d.ts +20 -5
- package/dist/resource_clients/actor_version.js +19 -9
- package/dist/resource_clients/actor_version_collection.d.ts +11 -4
- package/dist/resource_clients/actor_version_collection.js +13 -4
- package/dist/resource_clients/build.d.ts +20 -7
- package/dist/resource_clients/build.js +35 -15
- package/dist/resource_clients/build_collection.d.ts +7 -5
- package/dist/resource_clients/build_collection.js +4 -1
- package/dist/resource_clients/dataset.d.ts +53 -35
- package/dist/resource_clients/dataset.js +105 -80
- package/dist/resource_clients/dataset_collection.d.ts +10 -6
- package/dist/resource_clients/dataset_collection.js +13 -2
- package/dist/resource_clients/key_value_store.d.ts +62 -20
- package/dist/resource_clients/key_value_store.js +78 -40
- package/dist/resource_clients/key_value_store_collection.d.ts +10 -6
- package/dist/resource_clients/key_value_store_collection.js +13 -2
- package/dist/resource_clients/log.d.ts +13 -20
- package/dist/resource_clients/log.js +141 -59
- package/dist/resource_clients/request_queue.d.ts +68 -31
- package/dist/resource_clients/request_queue.js +173 -91
- package/dist/resource_clients/request_queue_collection.d.ts +9 -5
- package/dist/resource_clients/request_queue_collection.js +9 -3
- package/dist/resource_clients/run.d.ts +119 -28
- package/dist/resource_clients/run.js +190 -49
- package/dist/resource_clients/run_collection.d.ts +5 -3
- package/dist/resource_clients/run_collection.js +4 -1
- package/dist/resource_clients/schedule.d.ts +16 -7
- package/dist/resource_clients/schedule.js +30 -22
- package/dist/resource_clients/schedule_collection.d.ts +6 -2
- package/dist/resource_clients/schedule_collection.js +9 -3
- package/dist/resource_clients/store_collection.d.ts +6 -4
- package/dist/resource_clients/store_collection.js +4 -1
- package/dist/resource_clients/task.d.ts +36 -13
- package/dist/resource_clients/task.js +79 -46
- package/dist/resource_clients/task_collection.d.ts +9 -5
- package/dist/resource_clients/task_collection.js +9 -3
- package/dist/resource_clients/user.d.ts +19 -10
- package/dist/resource_clients/user.js +42 -38
- package/dist/resource_clients/webhook.d.ts +17 -8
- package/dist/resource_clients/webhook.js +30 -22
- package/dist/resource_clients/webhook_collection.d.ts +8 -4
- package/dist/resource_clients/webhook_collection.js +9 -3
- package/dist/resource_clients/webhook_dispatch.d.ts +4 -1
- package/dist/resource_clients/webhook_dispatch.js +7 -2
- package/dist/resource_clients/webhook_dispatch_collection.d.ts +3 -1
- package/dist/resource_clients/webhook_dispatch_collection.js +4 -1
- package/dist/runtime/node.d.ts +6 -0
- package/dist/runtime/node.js +49 -0
- package/dist/runtime/types.d.ts +52 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/web.d.ts +5 -0
- package/dist/runtime/web.js +18 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/statistics.d.ts +1 -6
- package/dist/statistics.js +2 -2
- package/dist/timeouts.d.ts +84 -0
- package/dist/timeouts.js +37 -0
- package/dist/timezones.d.ts +1 -1
- package/dist/utils.d.ts +82 -45
- package/dist/utils.js +197 -146
- package/dist/wait_for_resources.d.ts +8 -0
- package/dist/wait_for_resources.js +39 -0
- package/package.json +32 -29
- package/dist/http_client.d.ts +0 -88
- package/dist/http_client.js +0 -255
- package/dist/interceptors.d.ts +0 -19
- package/dist/interceptors.js +0 -105
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ACT_JOB_TERMINAL_STATUSES } from '@apify/consts';
|
|
2
|
-
import { catchNotFoundOrThrow, parseResponse } from '../utils.js';
|
|
2
|
+
import { catchNotFoundForResourceOrThrow, catchNotFoundOrThrow, parseResponse, sleep } from '../utils.js';
|
|
3
3
|
import { ApiClient } from './api_client.js';
|
|
4
4
|
/**
|
|
5
5
|
* We need to supply some number for the API,
|
|
@@ -7,65 +7,97 @@ import { ApiClient } from './api_client.js';
|
|
|
7
7
|
* 999999 seconds is more than 10 days.
|
|
8
8
|
*/
|
|
9
9
|
const MAX_WAIT_FOR_FINISH = 999999;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/** The API holds a `waitForFinish` response for at most a minute, however long the parameter asks for. */
|
|
11
|
+
const MAX_WAIT_FOR_FINISH_HOLD_SECS = 60;
|
|
12
|
+
/** How long `waitForFinish()` keeps polling a job that returns 404, to ride out replica lag, before it gives up. */
|
|
13
|
+
const MAX_WAIT_WHEN_JOB_NOT_EXIST_MILLIS = 3000;
|
|
13
14
|
/**
|
|
14
15
|
* Resource client.
|
|
15
16
|
* @private
|
|
16
17
|
*/
|
|
17
18
|
export class ResourceClient extends ApiClient {
|
|
18
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Picks the timeout of a request that asks the API to hold its response with `waitForFinish`. The request
|
|
21
|
+
* gets the hold the caller asked for plus the round trip its tier allows, so the client does not abort a
|
|
22
|
+
* request while the API is still holding it - which for `start()` and `build()` would retry a call that
|
|
23
|
+
* creates a resource. An explicit per-call `timeoutSecs` is used as given.
|
|
24
|
+
*/
|
|
25
|
+
timeoutForWaitForFinish(timeoutSecs, tier, waitForFinishSecs) {
|
|
26
|
+
if (timeoutSecs !== undefined)
|
|
27
|
+
return timeoutSecs;
|
|
28
|
+
if (waitForFinishSecs === undefined)
|
|
29
|
+
return tier;
|
|
30
|
+
const holdSecs = Math.min(waitForFinishSecs, MAX_WAIT_FOR_FINISH_HOLD_SECS);
|
|
31
|
+
if (holdSecs <= 0)
|
|
32
|
+
return tier;
|
|
33
|
+
return holdSecs + this.httpClient.timeoutMillis[tier] / 1000;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A 404 resolves to `undefined` only when the client names its resource by ID. A chained client without one, such
|
|
37
|
+
* as `run.dataset()`, throws it instead (see `catchNotFoundForResourceOrThrow()`).
|
|
38
|
+
*/
|
|
39
|
+
async getResource(schema, options, timeoutSecs, signal) {
|
|
19
40
|
const requestOpts = {
|
|
20
|
-
url: this.
|
|
41
|
+
url: this.buildUrl(),
|
|
21
42
|
method: 'GET',
|
|
22
|
-
params: this.
|
|
23
|
-
|
|
43
|
+
params: this.buildParams(options),
|
|
44
|
+
timeoutSecs,
|
|
45
|
+
signal,
|
|
24
46
|
};
|
|
25
47
|
try {
|
|
26
48
|
const response = await this.httpClient.call(requestOpts);
|
|
27
49
|
return parseResponse(response, schema);
|
|
28
50
|
}
|
|
29
51
|
catch (err) {
|
|
30
|
-
|
|
52
|
+
catchNotFoundForResourceOrThrow(err, this.id);
|
|
31
53
|
}
|
|
32
54
|
return undefined;
|
|
33
55
|
}
|
|
34
|
-
async
|
|
56
|
+
async updateResource(schema, newFields, timeoutSecs, signal) {
|
|
35
57
|
const response = await this.httpClient.call({
|
|
36
|
-
url: this.
|
|
58
|
+
url: this.buildUrl(),
|
|
37
59
|
method: 'PUT',
|
|
38
|
-
params: this.
|
|
60
|
+
params: this.buildParams(),
|
|
39
61
|
data: newFields,
|
|
40
|
-
|
|
62
|
+
timeoutSecs,
|
|
63
|
+
signal,
|
|
41
64
|
});
|
|
42
65
|
return parseResponse(response, schema);
|
|
43
66
|
}
|
|
44
|
-
|
|
67
|
+
/**
|
|
68
|
+
* A 404 is swallowed, keeping the DELETE idempotent, only when the client names its resource by ID. A chained client
|
|
69
|
+
* without one throws it instead (see `catchNotFoundForResourceOrThrow()`).
|
|
70
|
+
*/
|
|
71
|
+
async deleteResource(timeoutSecs, signal) {
|
|
45
72
|
try {
|
|
46
73
|
await this.httpClient.call({
|
|
47
|
-
url: this.
|
|
74
|
+
url: this.buildUrl(),
|
|
48
75
|
method: 'DELETE',
|
|
49
|
-
params: this.
|
|
50
|
-
|
|
76
|
+
params: this.buildParams(),
|
|
77
|
+
timeoutSecs,
|
|
78
|
+
signal,
|
|
51
79
|
});
|
|
52
80
|
}
|
|
53
81
|
catch (err) {
|
|
54
|
-
|
|
82
|
+
catchNotFoundForResourceOrThrow(err, this.id);
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
85
|
/**
|
|
58
86
|
* This function is used in Build and Run endpoints so it's kept
|
|
59
87
|
* here to stay DRY.
|
|
60
88
|
*/
|
|
61
|
-
async
|
|
62
|
-
const { waitSecs = MAX_WAIT_FOR_FINISH } = options;
|
|
89
|
+
async waitForJobFinish(schema, options = {}) {
|
|
90
|
+
const { waitSecs = MAX_WAIT_FOR_FINISH, timeoutSecs = 'noTimeout', signal } = options;
|
|
63
91
|
const waitMillis = waitSecs * 1000;
|
|
64
92
|
let job;
|
|
93
|
+
let notFoundSince;
|
|
94
|
+
let notFoundError;
|
|
65
95
|
const startedAt = Date.now();
|
|
66
96
|
const shouldRepeat = () => {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
if (now - startedAt >= waitMillis)
|
|
99
|
+
return false;
|
|
100
|
+
if (notFoundSince !== undefined && now - notFoundSince >= MAX_WAIT_WHEN_JOB_NOT_EXIST_MILLIS)
|
|
69
101
|
return false;
|
|
70
102
|
const hasJobEnded = job && ACT_JOB_TERMINAL_STATUSES.includes(job.status);
|
|
71
103
|
return !hasJobEnded;
|
|
@@ -75,30 +107,32 @@ export class ResourceClient extends ApiClient {
|
|
|
75
107
|
const remainingWaitSeconds = Math.round((waitMillis - millisSinceStart) / 1000);
|
|
76
108
|
const waitForFinish = Math.max(0, remainingWaitSeconds);
|
|
77
109
|
const requestOpts = {
|
|
78
|
-
url: this.
|
|
110
|
+
url: this.buildUrl(),
|
|
79
111
|
method: 'GET',
|
|
80
|
-
params: this.
|
|
112
|
+
params: this.buildParams({ waitForFinish }),
|
|
113
|
+
timeoutSecs,
|
|
114
|
+
signal,
|
|
81
115
|
};
|
|
82
116
|
try {
|
|
83
117
|
const response = await this.httpClient.call(requestOpts);
|
|
84
118
|
job = parseResponse(response, schema);
|
|
119
|
+
notFoundSince = undefined;
|
|
85
120
|
}
|
|
86
121
|
catch (err) {
|
|
87
122
|
catchNotFoundOrThrow(err);
|
|
88
123
|
job = undefined;
|
|
124
|
+
notFoundSince ??= Date.now();
|
|
125
|
+
notFoundError = err;
|
|
89
126
|
}
|
|
90
127
|
// It might take some time for database replicas to get up-to-date,
|
|
91
128
|
// so getRun() might return null. Wait a little bit and try it again.
|
|
92
|
-
if (!job)
|
|
93
|
-
await
|
|
94
|
-
|
|
95
|
-
|
|
129
|
+
if (!job) {
|
|
130
|
+
await sleep(250, signal);
|
|
131
|
+
signal?.throwIfAborted();
|
|
132
|
+
}
|
|
96
133
|
} while (shouldRepeat());
|
|
97
|
-
if (!job)
|
|
98
|
-
|
|
99
|
-
const jobName = constructorName.match(/(\w+)Client/)[1].toLowerCase();
|
|
100
|
-
throw new Error(`Waiting for ${jobName} to finish failed. Cannot fetch actor ${jobName} details from the server.`);
|
|
101
|
-
}
|
|
134
|
+
if (!job)
|
|
135
|
+
throw notFoundError;
|
|
102
136
|
return job;
|
|
103
137
|
}
|
|
104
138
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
|
+
import type { Timeout, TimeoutOptions } from '../timeouts.js';
|
|
2
3
|
import type { PaginatedResponse, PaginationOptions } from '../utils.js';
|
|
3
4
|
import { ApiClient } from './api_client.js';
|
|
4
5
|
/**
|
|
@@ -9,11 +10,18 @@ export declare class ResourceCollectionClient extends ApiClient {
|
|
|
9
10
|
/**
|
|
10
11
|
* @private
|
|
11
12
|
*/
|
|
12
|
-
protected
|
|
13
|
+
protected listResources<T, R>(schema: z.ZodType, options: T | undefined, timeoutSecs: Timeout, signal?: AbortSignal): Promise<R>;
|
|
14
|
+
/**
|
|
15
|
+
* Lists an endpoint that returns every item in one response and ignores `offset` and `limit`. The result can be
|
|
16
|
+
* awaited as that response or iterated over its items, with a single request either way.
|
|
17
|
+
*/
|
|
18
|
+
protected listUnpaginatedResources<Data, R extends PaginatedResponse<Data>>(schema: z.ZodType, timeoutSecs: Timeout, signal?: AbortSignal): AsyncIterable<Data> & Promise<R>;
|
|
13
19
|
/**
|
|
14
20
|
* Returns async iterator to iterate through all items and Promise that can be awaited to get first page of results.
|
|
21
|
+
* `defaultTimeoutSecs` applies to every page request unless `options.timeoutSecs` overrides it, and
|
|
22
|
+
* `options.signal` goes to every page request.
|
|
15
23
|
*/
|
|
16
|
-
protected
|
|
17
|
-
protected
|
|
18
|
-
protected
|
|
24
|
+
protected listResourcesPaginated<T extends PaginationOptions & TimeoutOptions, Data, R extends PaginatedResponse<Data>>(schema: z.ZodType, options: T, defaultTimeoutSecs: Timeout): AsyncIterable<Data> & Promise<R>;
|
|
25
|
+
protected createResource<D, R>(schema: z.ZodType, resource: D, timeoutSecs: Timeout, signal?: AbortSignal): Promise<R>;
|
|
26
|
+
protected getOrCreateResource<D, R>(schema: z.ZodType, name: string | undefined, resource: D | undefined, timeoutSecs: Timeout, signal?: AbortSignal): Promise<R>;
|
|
19
27
|
}
|
|
@@ -8,35 +8,58 @@ export class ResourceCollectionClient extends ApiClient {
|
|
|
8
8
|
/**
|
|
9
9
|
* @private
|
|
10
10
|
*/
|
|
11
|
-
async
|
|
11
|
+
async listResources(schema, options, timeoutSecs, signal) {
|
|
12
12
|
const response = await this.httpClient.call({
|
|
13
|
-
url: this.
|
|
13
|
+
url: this.buildUrl(),
|
|
14
14
|
method: 'GET',
|
|
15
|
-
params: this.
|
|
15
|
+
params: this.buildParams(options),
|
|
16
|
+
timeoutSecs,
|
|
17
|
+
signal,
|
|
16
18
|
});
|
|
17
19
|
return parseResponse(response, schema);
|
|
18
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Lists an endpoint that returns every item in one response and ignores `offset` and `limit`. The result can be
|
|
23
|
+
* awaited as that response or iterated over its items, with a single request either way.
|
|
24
|
+
*/
|
|
25
|
+
listUnpaginatedResources(schema, timeoutSecs, signal) {
|
|
26
|
+
const listPromise = this.listResources(schema, undefined, timeoutSecs, signal);
|
|
27
|
+
return Object.defineProperty(listPromise, Symbol.asyncIterator, {
|
|
28
|
+
async *value() {
|
|
29
|
+
yield* (await listPromise).items;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
19
33
|
/**
|
|
20
34
|
* Returns async iterator to iterate through all items and Promise that can be awaited to get first page of results.
|
|
35
|
+
* `defaultTimeoutSecs` applies to every page request unless `options.timeoutSecs` overrides it, and
|
|
36
|
+
* `options.signal` goes to every page request.
|
|
21
37
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
listResourcesPaginated(schema, options, defaultTimeoutSecs) {
|
|
39
|
+
// `timeoutSecs` and `signal` only apply to the page requests; they are not API parameters, so they must not
|
|
40
|
+
// reach the query string.
|
|
41
|
+
const { timeoutSecs = defaultTimeoutSecs, signal, ...listOptions } = options;
|
|
42
|
+
return this.listPaginatedFromCallback(async (pageOptions) => this.listResources(schema, pageOptions, timeoutSecs, signal), listOptions);
|
|
24
43
|
}
|
|
25
|
-
async
|
|
44
|
+
async createResource(schema, resource, timeoutSecs, signal) {
|
|
26
45
|
const response = await this.httpClient.call({
|
|
27
|
-
url: this.
|
|
46
|
+
url: this.buildUrl(),
|
|
28
47
|
method: 'POST',
|
|
29
|
-
params: this.
|
|
48
|
+
params: this.buildParams(),
|
|
30
49
|
data: resource,
|
|
50
|
+
timeoutSecs,
|
|
51
|
+
signal,
|
|
31
52
|
});
|
|
32
53
|
return parseResponse(response, schema);
|
|
33
54
|
}
|
|
34
|
-
async
|
|
55
|
+
async getOrCreateResource(schema, name, resource, timeoutSecs, signal) {
|
|
35
56
|
const response = await this.httpClient.call({
|
|
36
|
-
url: this.
|
|
57
|
+
url: this.buildUrl(),
|
|
37
58
|
method: 'POST',
|
|
38
|
-
params: this.
|
|
59
|
+
params: this.buildParams({ name }),
|
|
39
60
|
data: resource,
|
|
61
|
+
timeoutSecs,
|
|
62
|
+
signal,
|
|
40
63
|
});
|
|
41
64
|
return parseResponse(response, schema);
|
|
42
65
|
}
|
package/dist/body_parser.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { JsonArray, JsonObject } from 'type-fest';
|
|
2
2
|
/**
|
|
3
|
-
* Parses a
|
|
3
|
+
* Parses a binary response body using the provided content type header.
|
|
4
4
|
*
|
|
5
5
|
* - application/json is returned as a parsed object.
|
|
6
6
|
* - application/*xml and text/* are returned as strings.
|
|
7
7
|
* - everything else is returned as original body.
|
|
8
8
|
*
|
|
9
9
|
* If the header includes a charset, the body will be stringified only
|
|
10
|
-
* if the charset
|
|
10
|
+
* if the charset is an encoding `TextDecoder` knows.
|
|
11
11
|
*/
|
|
12
|
-
export declare function maybeParseBody(body:
|
|
13
|
-
export declare function isomorphicBufferToString(buffer: Buffer | ArrayBuffer, encoding: BufferEncoding): string;
|
|
12
|
+
export declare function maybeParseBody(body: ArrayBuffer | ArrayBufferView, contentTypeHeader: string): string | ArrayBuffer | ArrayBufferView | JsonObject | JsonArray;
|
package/dist/body_parser.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { isNode } from './utils.js';
|
|
1
|
+
import { parse as parseContentType } from 'content-type';
|
|
3
2
|
const CONTENT_TYPE_JSON = 'application/json';
|
|
4
3
|
const STRINGIFIABLE_CONTENT_TYPE_RXS = [new RegExp(`^${CONTENT_TYPE_JSON}`, 'i'), /^application\/.*xml$/i, /^text\//i];
|
|
5
4
|
/**
|
|
6
|
-
* Parses a
|
|
5
|
+
* Parses a binary response body using the provided content type header.
|
|
7
6
|
*
|
|
8
7
|
* - application/json is returned as a parsed object.
|
|
9
8
|
* - application/*xml and text/* are returned as strings.
|
|
10
9
|
* - everything else is returned as original body.
|
|
11
10
|
*
|
|
12
11
|
* If the header includes a charset, the body will be stringified only
|
|
13
|
-
* if the charset
|
|
12
|
+
* if the charset is an encoding `TextDecoder` knows.
|
|
14
13
|
*/
|
|
15
14
|
export function maybeParseBody(body, contentTypeHeader) {
|
|
16
15
|
let contentType;
|
|
17
16
|
let charset;
|
|
18
17
|
try {
|
|
19
|
-
const result =
|
|
18
|
+
const result = parseContentType(contentTypeHeader);
|
|
20
19
|
contentType = result.type;
|
|
21
20
|
charset = result.parameters.charset;
|
|
22
21
|
}
|
|
@@ -24,35 +23,29 @@ export function maybeParseBody(body, contentTypeHeader) {
|
|
|
24
23
|
// can't parse, keep original body
|
|
25
24
|
return body;
|
|
26
25
|
}
|
|
27
|
-
|
|
26
|
+
if (!isContentTypeStringifiable(contentType))
|
|
27
|
+
return body;
|
|
28
|
+
// If we can't successfully decode it, we return
|
|
28
29
|
// the original buffer rather than a mangled string.
|
|
29
|
-
|
|
30
|
+
const decoder = createDecoder(charset);
|
|
31
|
+
if (!decoder)
|
|
30
32
|
return body;
|
|
31
|
-
const dataString =
|
|
33
|
+
const dataString = decoder.decode(body);
|
|
32
34
|
return contentType === CONTENT_TYPE_JSON ? JSON.parse(dataString) : dataString;
|
|
33
35
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
function createDecoder(charset) {
|
|
37
|
+
try {
|
|
38
|
+
// No charset defaults to utf-8. A leading BOM is kept, so a BOM-prefixed CSV stored in a key-value store
|
|
39
|
+
// round-trips through `getRecord()` unchanged.
|
|
40
|
+
return new TextDecoder(charset, { ignoreBOM: true });
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// `TextDecoder` throws a `RangeError` for a label it does not know.
|
|
44
|
+
return undefined;
|
|
37
45
|
}
|
|
38
|
-
// Browser decoding only works with UTF-8.
|
|
39
|
-
const utf8decoder = new TextDecoder();
|
|
40
|
-
return utf8decoder.decode(new Uint8Array(buffer));
|
|
41
|
-
}
|
|
42
|
-
function isCharsetStringifiable(charset) {
|
|
43
|
-
if (!charset)
|
|
44
|
-
return true; // hope that it's utf-8
|
|
45
|
-
if (isNode())
|
|
46
|
-
return Buffer.isEncoding(charset);
|
|
47
|
-
const normalizedCharset = charset.toLowerCase().replace('-', '');
|
|
48
|
-
// Browsers only support decoding utf-8 buffers.
|
|
49
|
-
return normalizedCharset === 'utf8';
|
|
50
46
|
}
|
|
51
47
|
function isContentTypeStringifiable(contentType) {
|
|
52
48
|
if (!contentType)
|
|
53
49
|
return false; // keep buffer
|
|
54
50
|
return STRINGIFIABLE_CONTENT_TYPE_RXS.some((rx) => rx.test(contentType));
|
|
55
51
|
}
|
|
56
|
-
function areDataStringifiable(contentType, charset) {
|
|
57
|
-
return isContentTypeStringifiable(contentType) && isCharsetStringifiable(charset);
|
|
58
|
-
}
|