apify-client 3.0.0-beta.8 → 3.0.0-beta.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +10 -2
  2. package/dist/apify_api_error.d.ts +13 -27
  3. package/dist/apify_api_error.js +33 -23
  4. package/dist/apify_client.d.ts +125 -10
  5. package/dist/apify_client.js +133 -48
  6. package/dist/apify_client_error.d.ts +10 -0
  7. package/dist/apify_client_error.js +10 -0
  8. package/dist/base/api_client.d.ts +7 -7
  9. package/dist/base/api_client.js +36 -36
  10. package/dist/base/resource_client.d.ts +21 -8
  11. package/dist/base/resource_client.js +67 -33
  12. package/dist/base/resource_collection_client.d.ts +12 -4
  13. package/dist/base/resource_collection_client.js +34 -11
  14. package/dist/body_parser.d.ts +3 -4
  15. package/dist/body_parser.js +19 -26
  16. package/dist/bundle.js +27 -22
  17. package/dist/bundle.js.map +1 -1
  18. package/dist/generated/api.d.ts +3033 -971
  19. package/dist/generated/schemas.d.ts +2340 -932
  20. package/dist/generated/schemas.js +570 -229
  21. package/dist/http_clients/axios.d.ts +60 -0
  22. package/dist/http_clients/axios.js +117 -0
  23. package/dist/http_clients/base.d.ts +336 -0
  24. package/dist/http_clients/base.js +612 -0
  25. package/dist/http_clients/index.d.ts +4 -0
  26. package/dist/http_clients/index.js +2 -0
  27. package/dist/http_compressors/base.d.ts +40 -0
  28. package/dist/http_compressors/base.js +9 -0
  29. package/dist/http_compressors/brotli.d.ts +33 -0
  30. package/dist/http_compressors/brotli.js +39 -0
  31. package/dist/http_compressors/gzip.d.ts +33 -0
  32. package/dist/http_compressors/gzip.js +39 -0
  33. package/dist/http_compressors/index.d.ts +6 -0
  34. package/dist/http_compressors/index.js +2 -0
  35. package/dist/http_compressors/resolve.d.ts +23 -0
  36. package/dist/http_compressors/resolve.js +27 -0
  37. package/dist/index.d.ts +5 -1
  38. package/dist/index.js +4 -1
  39. package/dist/invalid_response_body_error.d.ts +14 -0
  40. package/dist/invalid_response_body_error.js +18 -0
  41. package/dist/models.d.ts +166 -243
  42. package/dist/models.js +6 -4
  43. package/dist/resource_clients/actor.d.ts +74 -34
  44. package/dist/resource_clients/actor.js +87 -56
  45. package/dist/resource_clients/actor_collection.d.ts +18 -12
  46. package/dist/resource_clients/actor_collection.js +11 -3
  47. package/dist/resource_clients/actor_env_var.d.ts +11 -4
  48. package/dist/resource_clients/actor_env_var.js +16 -6
  49. package/dist/resource_clients/actor_env_var_collection.d.ts +9 -4
  50. package/dist/resource_clients/actor_env_var_collection.js +11 -4
  51. package/dist/resource_clients/actor_version.d.ts +20 -5
  52. package/dist/resource_clients/actor_version.js +19 -9
  53. package/dist/resource_clients/actor_version_collection.d.ts +11 -4
  54. package/dist/resource_clients/actor_version_collection.js +13 -4
  55. package/dist/resource_clients/build.d.ts +20 -7
  56. package/dist/resource_clients/build.js +35 -15
  57. package/dist/resource_clients/build_collection.d.ts +7 -5
  58. package/dist/resource_clients/build_collection.js +4 -1
  59. package/dist/resource_clients/dataset.d.ts +81 -36
  60. package/dist/resource_clients/dataset.js +109 -120
  61. package/dist/resource_clients/dataset_collection.d.ts +10 -6
  62. package/dist/resource_clients/dataset_collection.js +13 -2
  63. package/dist/resource_clients/key_value_store.d.ts +106 -25
  64. package/dist/resource_clients/key_value_store.js +131 -50
  65. package/dist/resource_clients/key_value_store_collection.d.ts +10 -6
  66. package/dist/resource_clients/key_value_store_collection.js +13 -2
  67. package/dist/resource_clients/log.d.ts +40 -21
  68. package/dist/resource_clients/log.js +144 -66
  69. package/dist/resource_clients/request_queue.d.ts +76 -31
  70. package/dist/resource_clients/request_queue.js +178 -93
  71. package/dist/resource_clients/request_queue_collection.d.ts +9 -5
  72. package/dist/resource_clients/request_queue_collection.js +9 -3
  73. package/dist/resource_clients/run.d.ts +126 -28
  74. package/dist/resource_clients/run.js +193 -49
  75. package/dist/resource_clients/run_collection.d.ts +5 -3
  76. package/dist/resource_clients/run_collection.js +4 -1
  77. package/dist/resource_clients/schedule.d.ts +16 -7
  78. package/dist/resource_clients/schedule.js +30 -22
  79. package/dist/resource_clients/schedule_collection.d.ts +6 -2
  80. package/dist/resource_clients/schedule_collection.js +9 -3
  81. package/dist/resource_clients/store_collection.d.ts +6 -4
  82. package/dist/resource_clients/store_collection.js +4 -1
  83. package/dist/resource_clients/task.d.ts +36 -13
  84. package/dist/resource_clients/task.js +79 -46
  85. package/dist/resource_clients/task_collection.d.ts +9 -5
  86. package/dist/resource_clients/task_collection.js +9 -3
  87. package/dist/resource_clients/user.d.ts +19 -10
  88. package/dist/resource_clients/user.js +42 -38
  89. package/dist/resource_clients/webhook.d.ts +17 -8
  90. package/dist/resource_clients/webhook.js +30 -22
  91. package/dist/resource_clients/webhook_collection.d.ts +8 -4
  92. package/dist/resource_clients/webhook_collection.js +9 -3
  93. package/dist/resource_clients/webhook_dispatch.d.ts +4 -1
  94. package/dist/resource_clients/webhook_dispatch.js +7 -2
  95. package/dist/resource_clients/webhook_dispatch_collection.d.ts +3 -1
  96. package/dist/resource_clients/webhook_dispatch_collection.js +4 -1
  97. package/dist/response_validation_error.d.ts +2 -1
  98. package/dist/response_validation_error.js +2 -1
  99. package/dist/runtime/node.d.ts +6 -0
  100. package/dist/runtime/node.js +80 -0
  101. package/dist/runtime/types.d.ts +60 -0
  102. package/dist/runtime/types.js +1 -0
  103. package/dist/runtime/web.d.ts +5 -0
  104. package/dist/runtime/web.js +21 -0
  105. package/dist/schemas.d.ts +368 -4
  106. package/dist/schemas.js +26 -4
  107. package/dist/statistics.d.ts +1 -6
  108. package/dist/statistics.js +2 -2
  109. package/dist/timeouts.d.ts +84 -0
  110. package/dist/timeouts.js +37 -0
  111. package/dist/timezones.d.ts +1 -1
  112. package/dist/utils.d.ts +93 -58
  113. package/dist/utils.js +226 -158
  114. package/dist/wait_for_resources.d.ts +8 -0
  115. package/dist/wait_for_resources.js +39 -0
  116. package/package.json +32 -29
  117. package/dist/http_client.d.ts +0 -88
  118. package/dist/http_client.js +0 -255
  119. package/dist/interceptors.d.ts +0 -19
  120. package/dist/interceptors.js +0 -105
package/dist/utils.js CHANGED
@@ -4,7 +4,61 @@ import { parseArgument } from '@apify/validations';
4
4
  import { ResponseValidationError } from './response_validation_error.js';
5
5
  // @ts-ignore if we enable `resolveJsonModule`, we end up with a `src` folder in `dist`
6
6
  import packageJson from '../package.json' with { type: 'json' };
7
- const MIN_COMPRESS_BYTES = 1024;
7
+ /**
8
+ * Smallest request body, in bytes, that is worth compressing. A smaller body already fits in a single network
9
+ * packet, so compressing it costs CPU time without saving a round trip, and the compression format itself can
10
+ * make it larger.
11
+ * @internal
12
+ */
13
+ export const MIN_COMPRESS_BYTES = 1024;
14
+ const textEncoder = new TextEncoder();
15
+ // Only the version, so a bundler can drop the rest of the manifest.
16
+ export const { version } = packageJson;
17
+ /** Media type prefixes whose payloads carry their own compression, so compressing the request body is wasted work. */
18
+ const ALREADY_COMPRESSED_MEDIA_TYPE_PREFIXES = ['audio/', 'image/', 'video/'];
19
+ /** Exact media types whose payloads carry their own compression. */
20
+ const ALREADY_COMPRESSED_MEDIA_TYPES = new Set([
21
+ 'application/epub+zip',
22
+ 'application/gzip',
23
+ 'application/java-archive',
24
+ 'application/vnd.android.package-archive',
25
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
26
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
27
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
28
+ 'application/vnd.rar',
29
+ 'application/x-7z-compressed',
30
+ 'application/x-bzip',
31
+ 'application/x-bzip2',
32
+ 'application/x-gzip',
33
+ 'application/x-rar-compressed',
34
+ 'application/x-xz',
35
+ 'application/x-zip-compressed',
36
+ 'application/zip',
37
+ 'application/zstd',
38
+ 'font/woff',
39
+ 'font/woff2',
40
+ ]);
41
+ /** Uncompressed media types that sit under an already-compressed prefix, so compressing them still pays off. */
42
+ const COMPRESSIBLE_MEDIA_TYPES = new Set([
43
+ 'audio/aiff',
44
+ 'audio/basic',
45
+ 'audio/l16',
46
+ 'audio/l24',
47
+ 'audio/midi',
48
+ 'audio/vnd.wave',
49
+ 'audio/wav',
50
+ 'audio/wave',
51
+ 'audio/x-aiff',
52
+ 'audio/x-wav',
53
+ 'image/bmp',
54
+ 'image/tiff',
55
+ 'image/vnd.adobe.photoshop',
56
+ 'image/vnd.microsoft.icon',
57
+ 'image/x-icon',
58
+ 'image/x-ms-bmp',
59
+ ]);
60
+ /** Structured syntax suffixes marking a media type as text even under an already-compressed prefix (`image/svg+xml`). */
61
+ const COMPRESSIBLE_MEDIA_TYPE_SUFFIXES = ['+json', '+xml'];
8
62
  export { parseArgument };
9
63
  /**
10
64
  * Accepts any non-null, non-array object as a predicate for `z.custom()`.
@@ -26,36 +80,30 @@ export const anyObjectSchema = z.custom(isNonArrayObject, {
26
80
  // any tree-shaking bundler drops it and every message degrades to a bare "Invalid input". Passing it
27
81
  // in per parse keeps them intact without reaching into the zod config the whole process shares.
28
82
  const { localeError } = z.locales.en();
83
+ const RESPONSE_ENVELOPE_SCHEMA = z.object({ data: z.unknown() });
29
84
  /**
30
- * Turns a JSON API response into the value a resource method returns: unwraps the `data` envelope, converts the
31
- * date fields and validates the result against `schema`, one of the schemas generated from the OpenAPI
32
- * specification. The validated copy is what callers get, so it is the schema's output -- unknown fields and unknown
33
- * enum values included, since the schemas let both through, and URL fields normalized, since `z.url()` hands back
34
- * the parsed URL's serialization.
85
+ * Turns a JSON API response into the value a resource method returns: unwraps the `data` envelope and validates the
86
+ * result against `schema`, one of the schemas generated from the OpenAPI specification. The validated copy is what
87
+ * callers get, so it is the schema's output -- unknown fields and unknown enum values included, since the schemas let
88
+ * both through, date-time fields turned into `Date` objects, and URL fields normalized, since `z.url()` hands back the
89
+ * parsed URL's serialization.
35
90
  *
36
91
  * Throws {@link ResponseValidationError} when the response does not match the specification.
37
92
  * @internal
38
93
  */
39
- export function parseResponse(response, schema, shouldParseField = null) {
40
- const data = parseDateFields(pluckData(response.data), shouldParseField);
94
+ export function parseResponse(response, schema) {
95
+ const { method = 'GET', url = '' } = response.config;
96
+ const envelope = RESPONSE_ENVELOPE_SCHEMA.safeParse(response.data, { error: localeError });
97
+ if (!envelope.success) {
98
+ throw new ResponseValidationError(envelope.error, response.data, { method, url });
99
+ }
100
+ const { data } = envelope.data;
41
101
  const result = schema.safeParse(data, { error: localeError });
42
102
  if (!result.success) {
43
- const { method = 'GET', url = '' } = response.config;
44
103
  throw new ResponseValidationError(result.error, data, { method, url });
45
104
  }
46
105
  return result.data;
47
106
  }
48
- /**
49
- * Returns object's 'data' property or throws if parameter is not an object,
50
- * or an object without a 'data' property.
51
- */
52
- export function pluckData(obj) {
53
- if (typeof obj === 'object' && obj) {
54
- if (typeof obj.data !== 'undefined')
55
- return obj.data;
56
- }
57
- throw new Error(`Expected response object with a "data" property, but received: ${obj}`);
58
- }
59
107
  /**
60
108
  * Swallows a 404 Not Found API error and rethrows anything else.
61
109
  */
@@ -64,51 +112,16 @@ export function catchNotFoundOrThrow(err) {
64
112
  throw err;
65
113
  }
66
114
  /**
67
- * Traverses JSON structure and converts fields that end with "At" to a Date object (fields such as "modifiedAt" or
68
- * "createdAt").
69
- *
70
- * If you want parse other fields as well, you can provide a custom matcher function shouldParseField(). This
71
- * admittedly awkward approach allows this function to be reused for various purposes without introducing potential
72
- * breaking changes.
115
+ * Like `catchNotFoundOrThrow()`, but swallows the 404 only when the client names its resource by ID.
73
116
  *
74
- * If the field cannot be converted to Date, it is left as is.
117
+ * A chained client without an ID, such as `run.dataset()` or `run.log()`, requests a path where a 404 can mean either
118
+ * the parent or the default sub-resource is missing. The response cannot tell the two apart, so the error propagates.
119
+ * @internal
75
120
  */
76
- export function parseDateFields(input, shouldParseField = null, depth = 0) {
77
- // Don't go too deep to avoid stack overflows (especially if there is a circular reference). The depth of 4
78
- // corresponds to obj.items.[x].someArrayField.[y].field, which is what a list response looks like: it
79
- // nests one level deeper than the single resource it wraps, because both the item array and the nested
80
- // array spend a level.
81
- //
82
- // In a list response it also reaches one level into caller-owned blobs the API stores verbatim, so a
83
- // listed request's `userData.foo.somethingAt` comes back as a `Date` rather than the string it was
84
- // written as.
85
- // TODO: Consider removing this limitation. It might came across as an annoying surprise as it's not communicated.
86
- if (depth > 4) {
87
- return input;
88
- }
89
- if (Array.isArray(input))
90
- return input.map((child) => parseDateFields(child, shouldParseField, depth + 1));
91
- if (!input || typeof input !== 'object')
92
- return input;
93
- return Object.entries(input).reduce((output, [k, v]) => {
94
- const isValObject = !!v && typeof v === 'object';
95
- if (k.endsWith('At') || (shouldParseField && shouldParseField(k))) {
96
- if (v) {
97
- const d = new Date(v);
98
- output[k] = Number.isNaN(d.getTime()) ? v : d;
99
- }
100
- else {
101
- output[k] = v;
102
- }
103
- }
104
- else if (isValObject || Array.isArray(v)) {
105
- output[k] = parseDateFields(v, shouldParseField, depth + 1);
106
- }
107
- else {
108
- output[k] = v;
109
- }
110
- return output;
111
- }, {});
121
+ export function catchNotFoundForResourceOrThrow(err, resourceId) {
122
+ if (!resourceId)
123
+ throw err;
124
+ catchNotFoundOrThrow(err);
112
125
  }
113
126
  /**
114
127
  * Helper function that converts array of webhooks to base64 string
@@ -116,98 +129,105 @@ export function parseDateFields(input, shouldParseField = null, depth = 0) {
116
129
  export function stringifyWebhooksToBase64(webhooks) {
117
130
  if (!webhooks)
118
131
  return;
119
- const webhooksJson = JSON.stringify(webhooks);
120
- if (isNode()) {
121
- return Buffer.from(webhooksJson, 'utf8').toString('base64');
122
- }
123
- const encoder = new TextEncoder();
124
- const uint8Array = encoder.encode(webhooksJson);
125
- return btoa(String.fromCharCode(...uint8Array));
132
+ return bytesToBase64(textEncoder.encode(JSON.stringify(webhooks)));
126
133
  }
127
- let brotliCompressPromisified;
128
134
  /**
129
- * Brotli-compress the provided value.
135
+ * Encodes bytes as base64. `btoa()` takes a binary string, and the bytes are turned into one in slices,
136
+ * because spreading them all into a single `String.fromCharCode()` call overflows the argument limit on
137
+ * inputs of a few tens of kilobytes.
130
138
  */
131
- async function brotliValue(value) {
132
- if (!brotliCompressPromisified) {
133
- const { promisify } = await import('node:util');
134
- const { brotliCompress, constants } = await import('node:zlib');
135
- const compress = promisify(brotliCompress);
136
- const options = { params: { [constants.BROTLI_PARAM_QUALITY]: 6 } };
137
- brotliCompressPromisified = async (input) => compress(input, options);
139
+ export function bytesToBase64(bytes) {
140
+ const SLICE_LENGTH = 0x8000;
141
+ let binary = '';
142
+ for (let i = 0; i < bytes.length; i += SLICE_LENGTH) {
143
+ binary += String.fromCharCode(...bytes.subarray(i, i + SLICE_LENGTH));
138
144
  }
139
- return brotliCompressPromisified(value);
145
+ return btoa(binary);
140
146
  }
141
- let gzipPromisified;
142
147
  /**
143
- * Gzip-compress the provided value.
148
+ * Concatenates byte chunks into one array.
144
149
  */
145
- async function gzipValue(value) {
146
- if (!gzipPromisified) {
147
- const { promisify } = await import('node:util');
148
- const { gzip } = await import('node:zlib');
149
- gzipPromisified = promisify(gzip);
150
+ export function concatBytes(chunks) {
151
+ const result = new Uint8Array(chunks.reduce((length, chunk) => length + chunk.length, 0));
152
+ let offset = 0;
153
+ for (const chunk of chunks) {
154
+ result.set(chunk, offset);
155
+ offset += chunk.length;
150
156
  }
151
- return gzipPromisified(value);
157
+ return result;
152
158
  }
153
159
  /**
154
- * Compress the passed value using brotli, falling back to gzip. Returns undefined if the data is
155
- * too small / wrong type, or if neither algorithm is available.
160
+ * Views a request body as bytes: a string is UTF-8 encoded, binary values are viewed in place. Anything else
161
+ * - a stream, a `Blob`, form data - is `undefined`.
162
+ * @internal
156
163
  */
157
- export async function maybeCompressValue(value) {
158
- if (!isNode())
159
- return undefined;
160
- // Request compression is not that important so let's
161
- // skip it instead of throwing for unsupported types.
162
- if (typeof value !== 'string' && !Buffer.isBuffer(value))
163
- return undefined;
164
- const areDataLargeEnough = Buffer.byteLength(value) >= MIN_COMPRESS_BYTES;
165
- if (!areDataLargeEnough)
166
- return undefined;
167
- try {
168
- return { data: await brotliValue(value), encoding: 'br' };
169
- }
170
- catch {
171
- // Runtimes that only provide a partial `node:zlib` (bundler polyfills, edge runtimes with
172
- // Node compatibility shims) may not implement brotli, but usually do implement gzip.
173
- }
174
- try {
175
- return { data: await gzipValue(value), encoding: 'gzip' };
176
- }
177
- catch {
178
- // Same reasoning as above: compression is a best-effort optimization, so skip it instead
179
- // of failing the request.
164
+ export function toBytes(value) {
165
+ if (typeof value === 'string')
166
+ return textEncoder.encode(value);
167
+ if (!isBuffer(value))
180
168
  return undefined;
181
- }
169
+ return ArrayBuffer.isView(value)
170
+ ? new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
171
+ : new Uint8Array(value);
182
172
  }
183
173
  /**
184
- * Helper function slice the items from array to fit the max byte length.
174
+ * Decides whether a request body with the given content type is worth compressing.
175
+ *
176
+ * Images, audio, video and archives already carry their own compression. Running them through brotli or gzip
177
+ * burns CPU, holds a second full copy of the body in memory, and usually produces output slightly larger than
178
+ * the input. Formats that are raw despite such a media type, for example `image/bmp` or `audio/wav`, are still
179
+ * compressed. A body with no content type is assumed to be compressible.
180
+ * @internal
185
181
  */
186
- export function sliceArrayByByteLength(array, maxByteLength, startIndex) {
187
- const stringByteLength = (str) => (isNode() ? Buffer.byteLength(str) : new Blob([str]).size);
188
- const arrayByteLength = stringByteLength(JSON.stringify(array));
189
- if (arrayByteLength < maxByteLength)
190
- return array;
191
- const slicedArray = [];
192
- let byteLength = 2; // 2 bytes for the empty array []
193
- for (let i = 0; i < array.length; i++) {
194
- const item = array[i];
195
- const itemByteSize = stringByteLength(JSON.stringify(item));
196
- if (itemByteSize > maxByteLength) {
197
- throw new Error(`RequestQueueClient.batchAddRequests: The size of the request with index: ${startIndex + i} ` +
198
- `exceeds the maximum allowed size (${maxByteLength} bytes).`);
182
+ export function isCompressibleContentType(contentType) {
183
+ if (!contentType)
184
+ return true;
185
+ // `Content-Type` is case-insensitive and may carry parameters, for example `text/plain; charset=utf-8`.
186
+ const mediaType = contentType.split(';', 1)[0].trim().toLowerCase();
187
+ if (COMPRESSIBLE_MEDIA_TYPES.has(mediaType))
188
+ return true;
189
+ if (COMPRESSIBLE_MEDIA_TYPE_SUFFIXES.some((suffix) => mediaType.endsWith(suffix)))
190
+ return true;
191
+ if (ALREADY_COMPRESSED_MEDIA_TYPES.has(mediaType))
192
+ return false;
193
+ return !ALREADY_COMPRESSED_MEDIA_TYPE_PREFIXES.some((prefix) => mediaType.startsWith(prefix));
194
+ }
195
+ /**
196
+ * Reads an environment variable, on runtimes that have them.
197
+ */
198
+ export function getEnv(name) {
199
+ return typeof process !== 'undefined' ? process.env?.[name] : undefined;
200
+ }
201
+ /**
202
+ * Returns the UTF-8 byte length of a string.
203
+ */
204
+ export function utf8ByteLength(value) {
205
+ return textEncoder.encode(value).byteLength;
206
+ }
207
+ /**
208
+ * Splits JSON-serialized items into consecutive batches of at most `maxCount` items, each of which fits into a JSON
209
+ * array body - the items joined by commas between brackets - of at most `maxByteLength` bytes. The `byteLength` of an
210
+ * item is the UTF-8 byte length of its serialization. An item too large for a body of its own still gets one, so a
211
+ * caller that cannot send such an item has to reject it beforehand.
212
+ */
213
+ export function splitIntoJsonArrayBatches(items, { maxCount, maxByteLength }) {
214
+ const batches = [];
215
+ let batch = [];
216
+ // One byte for the opening bracket; each item then adds its own bytes plus one for the comma or the closing
217
+ // bracket that follows it.
218
+ let byteLength = 1;
219
+ for (const item of items) {
220
+ if (batch.length > 0 && (batch.length >= maxCount || byteLength + item.byteLength + 1 > maxByteLength)) {
221
+ batches.push(batch);
222
+ batch = [];
223
+ byteLength = 1;
199
224
  }
200
- if (byteLength + itemByteSize >= maxByteLength)
201
- break;
202
- byteLength += itemByteSize;
203
- slicedArray.push(item);
225
+ batch.push(item);
226
+ byteLength += item.byteLength + 1;
204
227
  }
205
- return slicedArray;
206
- }
207
- export function isNode() {
208
- if (typeof BROWSER_BUILD !== 'undefined')
209
- return false;
210
- return !!(typeof process !== 'undefined' && process.versions && process.versions.node);
228
+ if (batch.length > 0)
229
+ batches.push(batch);
230
+ return batches;
211
231
  }
212
232
  export function isBuffer(value) {
213
233
  // Tag checks rather than `instanceof`, to also match buffers from another realm. `isView()`
@@ -216,6 +236,13 @@ export function isBuffer(value) {
216
236
  return !isTagged(value, 'DataView');
217
237
  return isTagged(value, 'ArrayBuffer');
218
238
  }
239
+ /**
240
+ * Whether a value is a `Blob`, a `File` included. Unlike a stream, a blob can be read again for every attempt of a
241
+ * request, so a request with a blob body can be retried.
242
+ */
243
+ export function isBlob(value) {
244
+ return isTagged(value, 'Blob') || isTagged(value, 'File');
245
+ }
219
246
  function isTagged(value, tag) {
220
247
  return Object.prototype.toString.call(value) === `[object ${tag}]`;
221
248
  }
@@ -225,34 +252,28 @@ export function isStream(value) {
225
252
  const { on, pipe } = value;
226
253
  return typeof on === 'function' && typeof pipe === 'function';
227
254
  }
228
- export function getVersionData() {
229
- if (typeof BROWSER_BUILD !== 'undefined') {
230
- return { version: VERSION };
231
- }
232
- return packageJson;
233
- }
234
255
  /**
235
256
  * Helper class to create async iterators from paginated list endpoints.
236
257
  */
237
258
  export class RequestQueuePaginationIterator {
238
- maxPageLimit;
239
- getPage;
240
- limit;
241
- cursor;
259
+ #maxPageLimit;
260
+ #getPage;
261
+ #limit;
262
+ #cursor;
242
263
  constructor(options) {
243
- this.maxPageLimit = options.maxPageLimit;
244
- this.limit = options.limit;
245
- this.cursor = options.cursor;
246
- this.getPage = options.getPage;
264
+ this.#maxPageLimit = options.maxPageLimit;
265
+ this.#limit = options.limit;
266
+ this.#cursor = options.cursor;
267
+ this.#getPage = options.getPage;
247
268
  }
248
269
  async *[Symbol.asyncIterator]() {
249
- let nextCursor = this.cursor;
270
+ let nextCursor = this.#cursor;
250
271
  let iterateItemCount = 0;
251
272
  while (true) {
252
- const pageLimit = this.limit
253
- ? Math.min(this.maxPageLimit, this.limit - iterateItemCount)
254
- : this.maxPageLimit;
255
- const page = await this.getPage({
273
+ const pageLimit = this.#limit
274
+ ? Math.min(this.#maxPageLimit, this.#limit - iterateItemCount)
275
+ : this.#maxPageLimit;
276
+ const page = await this.#getPage({
256
277
  limit: pageLimit,
257
278
  cursor: nextCursor,
258
279
  });
@@ -262,12 +283,28 @@ export class RequestQueuePaginationIterator {
262
283
  yield page;
263
284
  iterateItemCount += page.items.length;
264
285
  // Limit reached stopping to iterate
265
- if ((this.limit && iterateItemCount >= this.limit) || !page.nextCursor)
286
+ if ((this.#limit && iterateItemCount >= this.#limit) || !page.nextCursor)
266
287
  return;
267
288
  nextCursor = page.nextCursor;
268
289
  }
269
290
  }
270
291
  }
292
+ /**
293
+ * The smaller of two page limits, for the `limit` query parameter. The API treats a `limit` of 0 as unset, so 0 here
294
+ * means no limit and loses to any other value.
295
+ * @internal
296
+ */
297
+ export function minForLimitParam(a, b) {
298
+ if (a === 0)
299
+ a = undefined;
300
+ if (b === 0)
301
+ b = undefined;
302
+ if (a === undefined)
303
+ return b;
304
+ if (b === undefined)
305
+ return a;
306
+ return Math.min(a, b);
307
+ }
271
308
  /**
272
309
  * Schema shape of {@link PaginationOptions}, to spread into every paginating client's list schema. One
273
310
  * copy stops it drifting from the interface.
@@ -278,6 +315,16 @@ export const paginationOptionsShape = {
278
315
  offset: z.number().min(0).optional(),
279
316
  chunkSize: z.number().positive().optional(),
280
317
  };
318
+ /**
319
+ * Key under which a page of dataset items carries the number of rows the API scanned to produce it, as a
320
+ * non-enumerable property that stays out of the page's public shape. The API applies `offset` and `limit` to the
321
+ * dataset's rows first and shapes the result afterwards: filters (`clean`, `skipEmpty`, `skipHidden`) drop items and
322
+ * `unwind` splits or drops them, so `items.length` can land on either side of the rows scanned. The
323
+ * `x-apify-pagination-count` header reports the scanned number, and the offset iterator advances and terminates by it
324
+ * alone. A page without the property falls back to `items.length`.
325
+ * @internal
326
+ */
327
+ export const SCANNED_COUNT = Symbol('scannedCount');
281
328
  export function cast(input) {
282
329
  return input;
283
330
  }
@@ -325,3 +372,24 @@ export function toPathSegment(value) {
325
372
  export function toPath(path) {
326
373
  return Array.isArray(path) ? path.map(toPathSegment).join('/') : path;
327
374
  }
375
+ /**
376
+ * Resolves after `millis`, or right away once `signal` aborts.
377
+ * @internal
378
+ */
379
+ export async function sleep(millis, signal) {
380
+ return new Promise((resolve) => {
381
+ if (signal?.aborted) {
382
+ resolve();
383
+ return;
384
+ }
385
+ const onAbort = () => {
386
+ clearTimeout(timer);
387
+ resolve();
388
+ };
389
+ const timer = setTimeout(() => {
390
+ signal?.removeEventListener('abort', onAbort);
391
+ resolve();
392
+ }, millis);
393
+ signal?.addEventListener('abort', onAbort, { once: true });
394
+ });
395
+ }
@@ -0,0 +1,8 @@
1
+ import type { Log } from '@apify/log';
2
+ /**
3
+ * Makes the `start` request, retrying it every {@link WAIT_FOR_RESOURCES_COOLDOWN_MILLIS} while it fails with one of
4
+ * {@link RESOURCE_LIMIT_ERROR_TYPES}. `true` retries until the request succeeds, a number of seconds bounds the
5
+ * retrying, after which the last error is thrown. Any other error is thrown right away.
6
+ * @internal
7
+ */
8
+ export declare function startWaitingForResources<T>(start: () => Promise<T>, waitForResources: boolean | number | undefined, logger: Log, signal?: AbortSignal): Promise<T>;
@@ -0,0 +1,39 @@
1
+ import { ApifyApiError } from './apify_api_error.js';
2
+ import { sleep } from './utils.js';
3
+ /**
4
+ * Error types the API rejects a run start with while the account has no free memory or concurrent-run slot for it.
5
+ * Both clear as other runs or builds finish.
6
+ */
7
+ const RESOURCE_LIMIT_ERROR_TYPES = new Set([
8
+ 'actor-memory-limit-exceeded',
9
+ 'concurrent-runs-limit-exceeded',
10
+ ]);
11
+ /** Cooldown between two attempts to start a run that was rejected for lack of resources. */
12
+ const WAIT_FOR_RESOURCES_COOLDOWN_MILLIS = 10_000;
13
+ /**
14
+ * Makes the `start` request, retrying it every {@link WAIT_FOR_RESOURCES_COOLDOWN_MILLIS} while it fails with one of
15
+ * {@link RESOURCE_LIMIT_ERROR_TYPES}. `true` retries until the request succeeds, a number of seconds bounds the
16
+ * retrying, after which the last error is thrown. Any other error is thrown right away.
17
+ * @internal
18
+ */
19
+ export async function startWaitingForResources(start, waitForResources, logger, signal) {
20
+ if (!waitForResources)
21
+ return start();
22
+ const deadline = waitForResources === true ? Infinity : Date.now() + waitForResources * 1000;
23
+ for (;;) {
24
+ try {
25
+ return await start();
26
+ }
27
+ catch (err) {
28
+ if (!(err instanceof ApifyApiError) || !RESOURCE_LIMIT_ERROR_TYPES.has(err.type ?? ''))
29
+ throw err;
30
+ const remainingMillis = deadline - Date.now();
31
+ if (remainingMillis <= 0)
32
+ throw err;
33
+ const delayMillis = Math.min(WAIT_FOR_RESOURCES_COOLDOWN_MILLIS, remainingMillis);
34
+ logger.info(`Not enough resources to start the run, retrying in ${Number((delayMillis / 1000).toPrecision(3))}s: ${err.message}`);
35
+ await sleep(delayMillis, signal);
36
+ signal?.throwIfAborted();
37
+ }
38
+ }
39
+ }
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "apify-client",
3
- "version": "3.0.0-beta.8",
3
+ "version": "3.0.0-beta.81",
4
4
  "description": "Apify API client for JavaScript",
5
5
  "engines": {
6
- "node": ">=22.0.0"
6
+ "node": ">=22.13.0"
7
7
  },
8
8
  "type": "module",
9
9
  "main": "dist/index.js",
10
10
  "types": "dist/index.d.ts",
11
11
  "browser": "dist/bundle.js",
12
12
  "unpkg": "dist/bundle.js",
13
+ "imports": {
14
+ "#runtime": {
15
+ "node": "./dist/runtime/node.js",
16
+ "default": "./dist/runtime/web.js"
17
+ }
18
+ },
13
19
  "exports": {
14
20
  "./package.json": "./package.json",
15
21
  "./browser": "./dist/bundle.js",
@@ -53,58 +59,54 @@
53
59
  ],
54
60
  "apify": {
55
61
  "openapiSpec": {
56
- "version": "v2-2026-08-31T125154Z"
62
+ "version": "v2-2026-10-29T100357Z"
57
63
  }
58
64
  },
59
65
  "dependencies": {
60
66
  "@apify/consts": "^3.0.1",
61
67
  "@apify/log": "^3.0.1",
62
- "@apify/utilities": "^3.0.1",
63
- "@apify/validations": "^1.1.0",
64
- "@crawlee/types": "^3.18.1",
68
+ "@apify/utilities": "^3.1.0",
69
+ "@apify/validations": "^1.1.1",
65
70
  "ansi-colors": "^4.1.3",
66
- "async-retry": "^1.3.3",
67
71
  "axios": "^1.20.0",
68
- "content-type": "^1.0.5",
69
- "proxy-agent": "^6.5.0",
72
+ "content-type": "^3.1.1",
73
+ "proxy-agent": "^8.0.2",
70
74
  "tslib": "^2.8.1",
71
- "type-fest": "^4.41.0",
72
- "zod": "^4.5.4"
75
+ "type-fest": "^5.10.0",
76
+ "zod": "^4.6.5"
73
77
  },
74
78
  "devDependencies": {
75
79
  "@apify/oxlint-config": "^0.3.0",
76
80
  "@apify/tsconfig": "^0.2.0",
77
- "@crawlee/puppeteer": "4.0.0-beta.165",
78
- "@rsbuild/core": "^2.2.3",
81
+ "@crawlee/puppeteer": "4.0.0-rc.1",
82
+ "@rsbuild/core": "^2.2.10",
79
83
  "@rsbuild/plugin-node-polyfill": "^1.4.6",
80
- "@rspack/cli": "^2.2.2",
81
- "@rspack/core": "^2.2.2",
82
- "@types/async-retry": "^1.4.9",
84
+ "@rspack/cli": "^2.2.8",
85
+ "@rspack/core": "^2.2.8",
83
86
  "@types/compression": "^1.8.1",
84
- "@types/content-type": "^1.1.9",
85
87
  "@types/express": "^5.0.6",
86
- "@types/node": "^24.13.3",
88
+ "@types/node": "^26.0.0",
87
89
  "ajv": "^8.20.0",
88
90
  "body-parser": "^2.3.0",
89
- "compression": "^1.8.1",
91
+ "compression": "^1.8.2",
90
92
  "esbuild": "0.28.2",
91
93
  "express": "^5.2.1",
92
- "openapi-typescript": "7.13.0",
93
- "oxfmt": "0.66.0",
94
- "oxlint": "1.81.0",
95
- "oxlint-tsgolint": "7.0.2001",
96
- "puppeteer": "^25.10.0",
94
+ "oxfmt": "0.73.0",
95
+ "oxlint": "1.90.0",
96
+ "oxlint-tsgolint": "7.0.2005",
97
+ "puppeteer": "^25.12.0",
97
98
  "rimraf": "^6.1.3",
98
- "rolldown": "^1.2.7",
99
- "typescript": "^6.0.3",
100
- "vitest": "^5.0.0",
101
- "webpack": "^5.110.3",
99
+ "rolldown": "^1.2.11",
100
+ "typescript": "^7.0.2",
101
+ "typescript-v6": "npm:typescript@^6.0.3",
102
+ "vitest": "^5.0.2",
103
+ "webpack": "^5.111.1",
102
104
  "webpack-cli": "^7.2.3"
103
105
  },
104
106
  "devEngines": {
105
107
  "packageManager": {
106
108
  "name": "pnpm",
107
- "version": "10.33.4",
109
+ "version": "12.16.1",
108
110
  "onFail": "warn"
109
111
  }
110
112
  },
@@ -119,6 +121,7 @@
119
121
  "lint:fix": "oxlint --type-aware --fix",
120
122
  "tsc-check-tests": "tsc --noEmit --project test/tsconfig.json",
121
123
  "tsc-check-scripts": "tsc --noEmit --project tsconfig.scripts.json",
124
+ "tsc-check-docs": "tsc --noEmit --project docs/tsconfig.json",
122
125
  "format": "oxfmt",
123
126
  "format:check": "oxfmt --check",
124
127
  "api:extract": "pnpm build && pnpm dlx github:apify/api-extractor-report --packages=. --extract-command='pnpm api:extract'",