apify 4.0.0-beta.21 → 4.0.0-beta.23
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/actor.js
CHANGED
|
@@ -633,7 +633,7 @@ export class Actor {
|
|
|
633
633
|
}
|
|
634
634
|
const dataset = await this.openDataset();
|
|
635
635
|
// Two code paths for charging:
|
|
636
|
-
// 1. Intercepted client:
|
|
636
|
+
// 1. Intercepted client: PpeAwareDatasetClient intercepts pushItems() calls, handling charging
|
|
637
637
|
// internally. This is needed because Crawlee's Dataset may call pushItems() directly,
|
|
638
638
|
// bypassing Actor.pushData(). We propagate eventName via AsyncLocalStorage context.
|
|
639
639
|
// 2. Direct charging: When using a non-patched client (e.g., forceCloud option or custom client),
|
|
@@ -1578,7 +1578,7 @@ export class Actor {
|
|
|
1578
1578
|
return Boolean(dataset.backend[USES_PUSH_DATA_INTERCEPTION]);
|
|
1579
1579
|
}
|
|
1580
1580
|
async pushDataViaInterceptedClient(dataset, item, eventName) {
|
|
1581
|
-
//
|
|
1581
|
+
// PpeAwareDatasetClient will handle charging and item limiting.
|
|
1582
1582
|
// We only need to propagate `eventName` and (optionally) return aggregated charge info.
|
|
1583
1583
|
const context = {
|
|
1584
1584
|
eventName,
|
|
@@ -2,8 +2,9 @@ import type { DatasetBackend, DatasetBackendListOptions, DatasetInfo, Dictionary
|
|
|
2
2
|
import type { DatasetClient } from 'apify-client';
|
|
3
3
|
/**
|
|
4
4
|
* Implements crawlee v4's {@link DatasetBackend} interface on top of `apify-client`'s
|
|
5
|
-
* dataset API.
|
|
6
|
-
*
|
|
5
|
+
* dataset API. Mostly a thin method-mapping wrapper (`getMetadata`/`get`, `drop`/`delete`,
|
|
6
|
+
* `getData`/`listItems`), except `pushData`, which also splits large pushes into chunks
|
|
7
|
+
* fitting the API's payload size limit.
|
|
7
8
|
*
|
|
8
9
|
* @internal
|
|
9
10
|
*/
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { MAX_PAYLOAD_SIZE_BYTES } from '@apify/consts';
|
|
2
|
+
/** Slight reduction of the API's 9MB payload limit, to stay safely below it. */
|
|
3
|
+
const SAFETY_BUFFER_PERCENT = 0.01 / 100; // 0.01%
|
|
4
|
+
const EFFECTIVE_LIMIT_BYTES = MAX_PAYLOAD_SIZE_BYTES - Math.ceil(MAX_PAYLOAD_SIZE_BYTES * SAFETY_BUFFER_PERCENT);
|
|
5
|
+
/** Per-item ceiling — 2 bytes under the chunk limit, so even a lone item fits its `[]` wrapper. */
|
|
6
|
+
const MAX_ITEM_BYTES = EFFECTIVE_LIMIT_BYTES - 2;
|
|
1
7
|
/**
|
|
2
8
|
* Implements crawlee v4's {@link DatasetBackend} interface on top of `apify-client`'s
|
|
3
|
-
* dataset API.
|
|
4
|
-
*
|
|
9
|
+
* dataset API. Mostly a thin method-mapping wrapper (`getMetadata`/`get`, `drop`/`delete`,
|
|
10
|
+
* `getData`/`listItems`), except `pushData`, which also splits large pushes into chunks
|
|
11
|
+
* fitting the API's payload size limit.
|
|
5
12
|
*
|
|
6
13
|
* @internal
|
|
7
14
|
*/
|
|
@@ -25,9 +32,46 @@ export class ApifyDatasetBackend {
|
|
|
25
32
|
'Use `drop()` to delete the dataset entirely, or open a new dataset instead.');
|
|
26
33
|
}
|
|
27
34
|
async pushData(items) {
|
|
28
|
-
|
|
35
|
+
// The platform API rejects payloads over 9MB — split the items into chunks
|
|
36
|
+
// that fit, pushed sequentially to preserve item order.
|
|
37
|
+
const payloads = items.map((item, index) => serializeToSizeLimit(item, index));
|
|
38
|
+
for (const chunk of chunkBySize(payloads, EFFECTIVE_LIMIT_BYTES)) {
|
|
39
|
+
await this.client.pushItems(chunk);
|
|
40
|
+
}
|
|
29
41
|
}
|
|
30
42
|
async getData(options) {
|
|
31
43
|
return await this.client.listItems(options);
|
|
32
44
|
}
|
|
33
45
|
}
|
|
46
|
+
/** Serializes a dataset item, throwing if it alone exceeds the payload size limit. */
|
|
47
|
+
function serializeToSizeLimit(item, index) {
|
|
48
|
+
const payload = JSON.stringify(item);
|
|
49
|
+
const bytes = Buffer.byteLength(payload);
|
|
50
|
+
if (bytes > MAX_ITEM_BYTES) {
|
|
51
|
+
throw new Error(`Data item at index ${index} is too large (size: ${bytes} bytes, limit: ${MAX_ITEM_BYTES} bytes)`);
|
|
52
|
+
}
|
|
53
|
+
return payload;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Takes an array of JSON-serialized items and groups them into JSON array strings
|
|
57
|
+
* of at most `limitBytes` each, preserving item order. Assumes (and does not
|
|
58
|
+
* validate) that no single item exceeds the limit.
|
|
59
|
+
*/
|
|
60
|
+
function chunkBySize(payloads, limitBytes) {
|
|
61
|
+
const chunks = [];
|
|
62
|
+
let chunkBytes = Infinity; // Forces the first item to open a new chunk.
|
|
63
|
+
for (const payload of payloads) {
|
|
64
|
+
const bytes = Buffer.byteLength(payload);
|
|
65
|
+
if (chunkBytes + bytes + 1 <= limitBytes) {
|
|
66
|
+
// Fits into the current chunk — add 1 byte for the ',' separator.
|
|
67
|
+
chunks[chunks.length - 1].push(payload);
|
|
68
|
+
chunkBytes += bytes + 1;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Open a new chunk — add 2 bytes for the '[]' wrapper.
|
|
72
|
+
chunks.push([payload]);
|
|
73
|
+
chunkBytes = bytes + 2;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return chunks.map((chunk) => `[${chunk.join(',')}]`);
|
|
77
|
+
}
|
|
@@ -11,7 +11,8 @@ export declare const USES_PUSH_DATA_INTERCEPTION: unique symbol;
|
|
|
11
11
|
* Context of a single `Actor.pushData()` call, shared with the intercepted
|
|
12
12
|
* `pushItems()` calls so they can (1) know which event to charge and
|
|
13
13
|
* (2) aggregate the {@link ChargeResult} across the multiple `pushItems()`
|
|
14
|
-
* calls a single `pushData()` may trigger (
|
|
14
|
+
* calls a single `pushData()` may trigger (the backend splits pushes exceeding
|
|
15
|
+
* the API's payload size limit).
|
|
15
16
|
*/
|
|
16
17
|
export interface PpeAwarePushDataContext {
|
|
17
18
|
eventName: string | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apify",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.23",
|
|
4
4
|
"description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
"@apify/log": "^2.4.3",
|
|
60
60
|
"@apify/timeout": "^0.3.0",
|
|
61
61
|
"@apify/utilities": "^2.13.0",
|
|
62
|
-
"@crawlee/core": "^4.0.0-beta.
|
|
63
|
-
"@crawlee/types": "^4.0.0-beta.
|
|
64
|
-
"@crawlee/utils": "^4.0.0-beta.
|
|
62
|
+
"@crawlee/core": "^4.0.0-beta.115",
|
|
63
|
+
"@crawlee/types": "^4.0.0-beta.115",
|
|
64
|
+
"@crawlee/utils": "^4.0.0-beta.115",
|
|
65
65
|
"apify-client": "^2.23.4",
|
|
66
66
|
"semver": "^7.5.4",
|
|
67
67
|
"tslib": "^2.6.2",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"@types/tough-cookie": "^4.0.5",
|
|
80
80
|
"@types/ws": "^8.5.12",
|
|
81
81
|
"commitlint": "^21.0.0",
|
|
82
|
-
"crawlee": "^4.0.0-beta.
|
|
82
|
+
"crawlee": "^4.0.0-beta.115",
|
|
83
83
|
"globby": "^16.0.0",
|
|
84
84
|
"husky": "^9.1.7",
|
|
85
85
|
"lint-staged": "^17.0.0",
|