apify-client 3.0.0-beta.4 → 3.0.0-beta.40

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 (117) hide show
  1. package/README.md +13 -3
  2. package/dist/apify_api_error.d.ts +62 -21
  3. package/dist/apify_api_error.js +96 -23
  4. package/dist/apify_client.d.ts +125 -10
  5. package/dist/apify_client.js +133 -48
  6. package/dist/base/api_client.d.ts +7 -7
  7. package/dist/base/api_client.js +24 -16
  8. package/dist/base/resource_client.d.ts +23 -9
  9. package/dist/base/resource_client.js +57 -29
  10. package/dist/base/resource_collection_client.d.ts +8 -4
  11. package/dist/base/resource_collection_client.js +26 -15
  12. package/dist/body_parser.d.ts +3 -4
  13. package/dist/body_parser.js +19 -26
  14. package/dist/bundle.js +29 -22
  15. package/dist/bundle.js.map +1 -1
  16. package/dist/generated/api.d.ts +887 -322
  17. package/dist/generated/schemas.d.ts +6717 -0
  18. package/dist/generated/schemas.js +1527 -0
  19. package/dist/http_clients/axios.d.ts +60 -0
  20. package/dist/http_clients/axios.js +117 -0
  21. package/dist/http_clients/base.d.ts +333 -0
  22. package/dist/http_clients/base.js +563 -0
  23. package/dist/http_clients/index.d.ts +4 -0
  24. package/dist/http_clients/index.js +2 -0
  25. package/dist/http_compressors/base.d.ts +40 -0
  26. package/dist/http_compressors/base.js +9 -0
  27. package/dist/http_compressors/brotli.d.ts +33 -0
  28. package/dist/http_compressors/brotli.js +39 -0
  29. package/dist/http_compressors/gzip.d.ts +33 -0
  30. package/dist/http_compressors/gzip.js +39 -0
  31. package/dist/http_compressors/index.d.ts +6 -0
  32. package/dist/http_compressors/index.js +2 -0
  33. package/dist/http_compressors/resolve.d.ts +23 -0
  34. package/dist/http_compressors/resolve.js +27 -0
  35. package/dist/index.d.ts +5 -1
  36. package/dist/index.js +4 -1
  37. package/dist/invalid_response_body_error.d.ts +13 -0
  38. package/dist/invalid_response_body_error.js +17 -0
  39. package/dist/lazy_schema.d.ts +8 -0
  40. package/dist/lazy_schema.js +11 -0
  41. package/dist/models.d.ts +33 -61
  42. package/dist/resource_clients/actor.d.ts +46 -31
  43. package/dist/resource_clients/actor.js +78 -58
  44. package/dist/resource_clients/actor_collection.d.ts +9 -5
  45. package/dist/resource_clients/actor_collection.js +12 -3
  46. package/dist/resource_clients/actor_env_var.d.ts +10 -3
  47. package/dist/resource_clients/actor_env_var.js +17 -6
  48. package/dist/resource_clients/actor_env_var_collection.d.ts +14 -18
  49. package/dist/resource_clients/actor_env_var_collection.js +18 -11
  50. package/dist/resource_clients/actor_version.d.ts +18 -3
  51. package/dist/resource_clients/actor_version.js +20 -9
  52. package/dist/resource_clients/actor_version_collection.d.ts +16 -17
  53. package/dist/resource_clients/actor_version_collection.js +20 -11
  54. package/dist/resource_clients/build.d.ts +18 -6
  55. package/dist/resource_clients/build.js +37 -17
  56. package/dist/resource_clients/build_collection.d.ts +3 -1
  57. package/dist/resource_clients/build_collection.js +5 -1
  58. package/dist/resource_clients/dataset.d.ts +49 -31
  59. package/dist/resource_clients/dataset.js +102 -76
  60. package/dist/resource_clients/dataset_collection.d.ts +6 -2
  61. package/dist/resource_clients/dataset_collection.js +14 -2
  62. package/dist/resource_clients/key_value_store.d.ts +58 -16
  63. package/dist/resource_clients/key_value_store.js +80 -41
  64. package/dist/resource_clients/key_value_store_collection.d.ts +6 -2
  65. package/dist/resource_clients/key_value_store_collection.js +14 -2
  66. package/dist/resource_clients/log.d.ts +11 -19
  67. package/dist/resource_clients/log.js +64 -46
  68. package/dist/resource_clients/request_queue.d.ts +56 -26
  69. package/dist/resource_clients/request_queue.js +197 -116
  70. package/dist/resource_clients/request_queue_collection.d.ts +6 -2
  71. package/dist/resource_clients/request_queue_collection.js +10 -3
  72. package/dist/resource_clients/run.d.ts +52 -18
  73. package/dist/resource_clients/run.js +93 -47
  74. package/dist/resource_clients/run_collection.d.ts +3 -1
  75. package/dist/resource_clients/run_collection.js +5 -1
  76. package/dist/resource_clients/schedule.d.ts +16 -7
  77. package/dist/resource_clients/schedule.js +32 -21
  78. package/dist/resource_clients/schedule_collection.d.ts +6 -2
  79. package/dist/resource_clients/schedule_collection.js +10 -3
  80. package/dist/resource_clients/store_collection.d.ts +3 -1
  81. package/dist/resource_clients/store_collection.js +5 -1
  82. package/dist/resource_clients/task.d.ts +28 -10
  83. package/dist/resource_clients/task.js +71 -46
  84. package/dist/resource_clients/task_collection.d.ts +6 -2
  85. package/dist/resource_clients/task_collection.js +10 -3
  86. package/dist/resource_clients/user.d.ts +17 -8
  87. package/dist/resource_clients/user.js +43 -39
  88. package/dist/resource_clients/webhook.d.ts +14 -5
  89. package/dist/resource_clients/webhook.js +31 -22
  90. package/dist/resource_clients/webhook_collection.d.ts +6 -2
  91. package/dist/resource_clients/webhook_collection.js +10 -3
  92. package/dist/resource_clients/webhook_dispatch.d.ts +4 -1
  93. package/dist/resource_clients/webhook_dispatch.js +8 -2
  94. package/dist/resource_clients/webhook_dispatch_collection.d.ts +3 -1
  95. package/dist/resource_clients/webhook_dispatch_collection.js +5 -1
  96. package/dist/response_validation_error.d.ts +26 -0
  97. package/dist/response_validation_error.js +37 -0
  98. package/dist/runtime/node.d.ts +6 -0
  99. package/dist/runtime/node.js +49 -0
  100. package/dist/runtime/types.d.ts +52 -0
  101. package/dist/runtime/types.js +1 -0
  102. package/dist/runtime/web.d.ts +5 -0
  103. package/dist/runtime/web.js +18 -0
  104. package/dist/schemas.d.ts +15 -0
  105. package/dist/schemas.js +15 -0
  106. package/dist/statistics.d.ts +1 -6
  107. package/dist/statistics.js +2 -2
  108. package/dist/timeouts.d.ts +84 -0
  109. package/dist/timeouts.js +37 -0
  110. package/dist/timezones.d.ts +1 -1
  111. package/dist/utils.d.ts +86 -46
  112. package/dist/utils.js +218 -164
  113. package/package.json +32 -29
  114. package/dist/http_client.d.ts +0 -88
  115. package/dist/http_client.js +0 -255
  116. package/dist/interceptors.d.ts +0 -19
  117. package/dist/interceptors.js +0 -105
package/dist/utils.js CHANGED
@@ -1,11 +1,64 @@
1
1
  import { z } from 'zod';
2
+ import { NotFoundError } from './apify_api_error.js';
2
3
  import { parseArgument } from '@apify/validations';
4
+ import { ResponseValidationError } from './response_validation_error.js';
3
5
  // @ts-ignore if we enable `resolveJsonModule`, we end up with a `src` folder in `dist`
4
6
  import packageJson from '../package.json' with { type: 'json' };
5
- const NOT_FOUND_STATUS_CODE = 404;
6
- const RECORD_NOT_FOUND_TYPE = 'record-not-found';
7
- const RECORD_OR_TOKEN_NOT_FOUND_TYPE = 'record-or-token-not-found';
8
- 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'];
9
62
  export { parseArgument };
10
63
  /**
11
64
  * Accepts any non-null, non-array object as a predicate for `z.custom()`.
@@ -23,6 +76,29 @@ export function isNonArrayObject(value) {
23
76
  export const anyObjectSchema = z.custom(isNonArrayObject, {
24
77
  error: 'Invalid input: expected an object',
25
78
  });
79
+ // Zod installs its English locale as a module-level side effect but ships `"sideEffects": false`, so
80
+ // any tree-shaking bundler drops it and every message degrades to a bare "Invalid input". Passing it
81
+ // in per parse keeps them intact without reaching into the zod config the whole process shares.
82
+ const { localeError } = z.locales.en();
83
+ /**
84
+ * Turns a JSON API response into the value a resource method returns: unwraps the `data` envelope and validates the
85
+ * result against `schema`, one of the schemas generated from the OpenAPI specification. The validated copy is what
86
+ * callers get, so it is the schema's output -- unknown fields and unknown enum values included, since the schemas let
87
+ * both through, date-time fields turned into `Date` objects, and URL fields normalized, since `z.url()` hands back the
88
+ * parsed URL's serialization.
89
+ *
90
+ * Throws {@link ResponseValidationError} when the response does not match the specification.
91
+ * @internal
92
+ */
93
+ export function parseResponse(response, schema) {
94
+ const data = pluckData(response.data);
95
+ const result = schema.safeParse(data, { error: localeError });
96
+ if (!result.success) {
97
+ const { method = 'GET', url = '' } = response.config;
98
+ throw new ResponseValidationError(result.error, data, { method, url });
99
+ }
100
+ return result.data;
101
+ }
26
102
  /**
27
103
  * Returns object's 'data' property or throws if parameter is not an object,
28
104
  * or an object without a 'data' property.
@@ -35,62 +111,23 @@ export function pluckData(obj) {
35
111
  throw new Error(`Expected response object with a "data" property, but received: ${obj}`);
36
112
  }
37
113
  /**
38
- * If given HTTP error has NOT_FOUND_STATUS_CODE status code then returns undefined.
39
- * Otherwise rethrows error.
114
+ * Swallows a 404 Not Found API error and rethrows anything else.
40
115
  */
41
116
  export function catchNotFoundOrThrow(err) {
42
- const isNotFoundStatus = err.statusCode === NOT_FOUND_STATUS_CODE;
43
- const isNotFoundMessage = err.type === RECORD_NOT_FOUND_TYPE || err.type === RECORD_OR_TOKEN_NOT_FOUND_TYPE || err.httpMethod === 'head';
44
- const isNotFoundError = isNotFoundStatus && isNotFoundMessage;
45
- if (!isNotFoundError)
117
+ if (!(err instanceof NotFoundError))
46
118
  throw err;
47
119
  }
48
120
  /**
49
- * Traverses JSON structure and converts fields that end with "At" to a Date object (fields such as "modifiedAt" or
50
- * "createdAt").
51
- *
52
- * If you want parse other fields as well, you can provide a custom matcher function shouldParseField(). This
53
- * admittedly awkward approach allows this function to be reused for various purposes without introducing potential
54
- * breaking changes.
121
+ * Like `catchNotFoundOrThrow()`, but swallows the 404 only when the client names its resource by ID.
55
122
  *
56
- * If the field cannot be converted to Date, it is left as is.
123
+ * A chained client without an ID, such as `run.dataset()` or `run.log()`, requests a path where a 404 can mean either
124
+ * the parent or the default sub-resource is missing. The response cannot tell the two apart, so the error propagates.
125
+ * @internal
57
126
  */
58
- export function parseDateFields(input, shouldParseField = null, depth = 0) {
59
- // Don't go too deep to avoid stack overflows (especially if there is a circular reference). The depth of 4
60
- // corresponds to obj.items.[x].someArrayField.[y].field, which is what a list response looks like: it
61
- // nests one level deeper than the single resource it wraps, because both the item array and the nested
62
- // array spend a level.
63
- //
64
- // In a list response it also reaches one level into caller-owned blobs the API stores verbatim, so a
65
- // listed request's `userData.foo.somethingAt` comes back as a `Date` rather than the string it was
66
- // written as.
67
- // TODO: Consider removing this limitation. It might came across as an annoying surprise as it's not communicated.
68
- if (depth > 4) {
69
- return input;
70
- }
71
- if (Array.isArray(input))
72
- return input.map((child) => parseDateFields(child, shouldParseField, depth + 1));
73
- if (!input || typeof input !== 'object')
74
- return input;
75
- return Object.entries(input).reduce((output, [k, v]) => {
76
- const isValObject = !!v && typeof v === 'object';
77
- if (k.endsWith('At') || (shouldParseField && shouldParseField(k))) {
78
- if (v) {
79
- const d = new Date(v);
80
- output[k] = Number.isNaN(d.getTime()) ? v : d;
81
- }
82
- else {
83
- output[k] = v;
84
- }
85
- }
86
- else if (isValObject || Array.isArray(v)) {
87
- output[k] = parseDateFields(v, shouldParseField, depth + 1);
88
- }
89
- else {
90
- output[k] = v;
91
- }
92
- return output;
93
- }, {});
127
+ export function catchNotFoundForResourceOrThrow(err, resourceId) {
128
+ if (!resourceId)
129
+ throw err;
130
+ catchNotFoundOrThrow(err);
94
131
  }
95
132
  /**
96
133
  * Helper function that converts array of webhooks to base64 string
@@ -98,98 +135,105 @@ export function parseDateFields(input, shouldParseField = null, depth = 0) {
98
135
  export function stringifyWebhooksToBase64(webhooks) {
99
136
  if (!webhooks)
100
137
  return;
101
- const webhooksJson = JSON.stringify(webhooks);
102
- if (isNode()) {
103
- return Buffer.from(webhooksJson, 'utf8').toString('base64');
104
- }
105
- const encoder = new TextEncoder();
106
- const uint8Array = encoder.encode(webhooksJson);
107
- return btoa(String.fromCharCode(...uint8Array));
138
+ return bytesToBase64(textEncoder.encode(JSON.stringify(webhooks)));
108
139
  }
109
- let brotliCompressPromisified;
110
140
  /**
111
- * Brotli-compress the provided value.
141
+ * Encodes bytes as base64. `btoa()` takes a binary string, and the bytes are turned into one in slices,
142
+ * because spreading them all into a single `String.fromCharCode()` call overflows the argument limit on
143
+ * inputs of a few tens of kilobytes.
112
144
  */
113
- async function brotliValue(value) {
114
- if (!brotliCompressPromisified) {
115
- const { promisify } = await import('node:util');
116
- const { brotliCompress, constants } = await import('node:zlib');
117
- const compress = promisify(brotliCompress);
118
- const options = { params: { [constants.BROTLI_PARAM_QUALITY]: 6 } };
119
- brotliCompressPromisified = async (input) => compress(input, options);
145
+ export function bytesToBase64(bytes) {
146
+ const SLICE_LENGTH = 0x8000;
147
+ let binary = '';
148
+ for (let i = 0; i < bytes.length; i += SLICE_LENGTH) {
149
+ binary += String.fromCharCode(...bytes.subarray(i, i + SLICE_LENGTH));
120
150
  }
121
- return brotliCompressPromisified(value);
151
+ return btoa(binary);
122
152
  }
123
- let gzipPromisified;
124
153
  /**
125
- * Gzip-compress the provided value.
154
+ * Concatenates byte chunks into one array.
126
155
  */
127
- async function gzipValue(value) {
128
- if (!gzipPromisified) {
129
- const { promisify } = await import('node:util');
130
- const { gzip } = await import('node:zlib');
131
- gzipPromisified = promisify(gzip);
156
+ export function concatBytes(chunks) {
157
+ const result = new Uint8Array(chunks.reduce((length, chunk) => length + chunk.length, 0));
158
+ let offset = 0;
159
+ for (const chunk of chunks) {
160
+ result.set(chunk, offset);
161
+ offset += chunk.length;
132
162
  }
133
- return gzipPromisified(value);
163
+ return result;
134
164
  }
135
165
  /**
136
- * Compress the passed value using brotli, falling back to gzip. Returns undefined if the data is
137
- * too small / wrong type, or if neither algorithm is available.
166
+ * Views a request body as bytes: a string is UTF-8 encoded, binary values are viewed in place. Anything else
167
+ * - a stream, a `Blob`, form data - is `undefined`.
168
+ * @internal
138
169
  */
139
- export async function maybeCompressValue(value) {
140
- if (!isNode())
141
- return undefined;
142
- // Request compression is not that important so let's
143
- // skip it instead of throwing for unsupported types.
144
- if (typeof value !== 'string' && !Buffer.isBuffer(value))
145
- return undefined;
146
- const areDataLargeEnough = Buffer.byteLength(value) >= MIN_COMPRESS_BYTES;
147
- if (!areDataLargeEnough)
170
+ export function toBytes(value) {
171
+ if (typeof value === 'string')
172
+ return textEncoder.encode(value);
173
+ if (!isBuffer(value))
148
174
  return undefined;
149
- try {
150
- return { data: await brotliValue(value), encoding: 'br' };
151
- }
152
- catch {
153
- // Runtimes that only provide a partial `node:zlib` (bundler polyfills, edge runtimes with
154
- // Node compatibility shims) may not implement brotli, but usually do implement gzip.
155
- }
156
- try {
157
- return { data: await gzipValue(value), encoding: 'gzip' };
158
- }
159
- catch {
160
- // Same reasoning as above: compression is a best-effort optimization, so skip it instead
161
- // of failing the request.
162
- return undefined;
163
- }
175
+ return ArrayBuffer.isView(value)
176
+ ? new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
177
+ : new Uint8Array(value);
178
+ }
179
+ /**
180
+ * Decides whether a request body with the given content type is worth compressing.
181
+ *
182
+ * Images, audio, video and archives already carry their own compression. Running them through brotli or gzip
183
+ * burns CPU, holds a second full copy of the body in memory, and usually produces output slightly larger than
184
+ * the input. Formats that are raw despite such a media type, for example `image/bmp` or `audio/wav`, are still
185
+ * compressed. A body with no content type is assumed to be compressible.
186
+ * @internal
187
+ */
188
+ export function isCompressibleContentType(contentType) {
189
+ if (!contentType)
190
+ return true;
191
+ // `Content-Type` is case-insensitive and may carry parameters, for example `text/plain; charset=utf-8`.
192
+ const mediaType = contentType.split(';', 1)[0].trim().toLowerCase();
193
+ if (COMPRESSIBLE_MEDIA_TYPES.has(mediaType))
194
+ return true;
195
+ if (COMPRESSIBLE_MEDIA_TYPE_SUFFIXES.some((suffix) => mediaType.endsWith(suffix)))
196
+ return true;
197
+ if (ALREADY_COMPRESSED_MEDIA_TYPES.has(mediaType))
198
+ return false;
199
+ return !ALREADY_COMPRESSED_MEDIA_TYPE_PREFIXES.some((prefix) => mediaType.startsWith(prefix));
164
200
  }
165
201
  /**
166
- * Helper function slice the items from array to fit the max byte length.
202
+ * Reads an environment variable, on runtimes that have them.
167
203
  */
168
- export function sliceArrayByByteLength(array, maxByteLength, startIndex) {
169
- const stringByteLength = (str) => (isNode() ? Buffer.byteLength(str) : new Blob([str]).size);
170
- const arrayByteLength = stringByteLength(JSON.stringify(array));
171
- if (arrayByteLength < maxByteLength)
172
- return array;
173
- const slicedArray = [];
174
- let byteLength = 2; // 2 bytes for the empty array []
175
- for (let i = 0; i < array.length; i++) {
176
- const item = array[i];
177
- const itemByteSize = stringByteLength(JSON.stringify(item));
178
- if (itemByteSize > maxByteLength) {
179
- throw new Error(`RequestQueueClient.batchAddRequests: The size of the request with index: ${startIndex + i} ` +
180
- `exceeds the maximum allowed size (${maxByteLength} bytes).`);
204
+ export function getEnv(name) {
205
+ return typeof process !== 'undefined' ? process.env?.[name] : undefined;
206
+ }
207
+ /**
208
+ * Returns the UTF-8 byte length of a string.
209
+ */
210
+ export function utf8ByteLength(value) {
211
+ return textEncoder.encode(value).byteLength;
212
+ }
213
+ /**
214
+ * Splits JSON-serialized items into consecutive batches of at most `maxCount` items, each of which fits into a JSON
215
+ * array body - the items joined by commas between brackets - of at most `maxByteLength` bytes. The `byteLength` of an
216
+ * 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
217
+ * caller that cannot send such an item has to reject it beforehand.
218
+ */
219
+ export function splitIntoJsonArrayBatches(items, { maxCount, maxByteLength }) {
220
+ const batches = [];
221
+ let batch = [];
222
+ // One byte for the opening bracket; each item then adds its own bytes plus one for the comma or the closing
223
+ // bracket that follows it.
224
+ let byteLength = 1;
225
+ for (const item of items) {
226
+ if (batch.length > 0 && (batch.length >= maxCount || byteLength + item.byteLength + 1 > maxByteLength)) {
227
+ batches.push(batch);
228
+ batch = [];
229
+ byteLength = 1;
181
230
  }
182
- if (byteLength + itemByteSize >= maxByteLength)
183
- break;
184
- byteLength += itemByteSize;
185
- slicedArray.push(item);
231
+ batch.push(item);
232
+ byteLength += item.byteLength + 1;
186
233
  }
187
- return slicedArray;
188
- }
189
- export function isNode() {
190
- if (typeof BROWSER_BUILD !== 'undefined')
191
- return false;
192
- return !!(typeof process !== 'undefined' && process.versions && process.versions.node);
234
+ if (batch.length > 0)
235
+ batches.push(batch);
236
+ return batches;
193
237
  }
194
238
  export function isBuffer(value) {
195
239
  // Tag checks rather than `instanceof`, to also match buffers from another realm. `isView()`
@@ -207,41 +251,30 @@ export function isStream(value) {
207
251
  const { on, pipe } = value;
208
252
  return typeof on === 'function' && typeof pipe === 'function';
209
253
  }
210
- export function getVersionData() {
211
- if (typeof BROWSER_BUILD !== 'undefined') {
212
- return { version: VERSION };
213
- }
214
- return packageJson;
215
- }
216
254
  /**
217
- * Helper class to create async iterators from paginated list endpoints with exclusive start key.
255
+ * Helper class to create async iterators from paginated list endpoints.
218
256
  */
219
257
  export class RequestQueuePaginationIterator {
220
- maxPageLimit;
221
- getPage;
222
- limit;
223
- exclusiveStartId;
224
- cursor;
258
+ #maxPageLimit;
259
+ #getPage;
260
+ #limit;
261
+ #cursor;
225
262
  constructor(options) {
226
- this.maxPageLimit = options.maxPageLimit;
227
- this.limit = options.limit;
228
- this.exclusiveStartId = options.exclusiveStartId;
229
- this.cursor = options.cursor;
230
- this.getPage = options.getPage;
263
+ this.#maxPageLimit = options.maxPageLimit;
264
+ this.#limit = options.limit;
265
+ this.#cursor = options.cursor;
266
+ this.#getPage = options.getPage;
231
267
  }
232
268
  async *[Symbol.asyncIterator]() {
233
- let nextCursor = this.cursor;
234
- // allow using exclusiveStartId for the first page, but then we'll delete it to avoid using it for any later page
235
- let nextExclusiveStartId = this.exclusiveStartId;
269
+ let nextCursor = this.#cursor;
236
270
  let iterateItemCount = 0;
237
271
  while (true) {
238
- const pageLimit = this.limit
239
- ? Math.min(this.maxPageLimit, this.limit - iterateItemCount)
240
- : this.maxPageLimit;
241
- const page = await this.getPage({
272
+ const pageLimit = this.#limit
273
+ ? Math.min(this.#maxPageLimit, this.#limit - iterateItemCount)
274
+ : this.#maxPageLimit;
275
+ const page = await this.#getPage({
242
276
  limit: pageLimit,
243
277
  cursor: nextCursor,
244
- exclusiveStartId: nextExclusiveStartId,
245
278
  });
246
279
  // There are no more pages to iterate
247
280
  if (page.items.length === 0)
@@ -249,10 +282,9 @@ export class RequestQueuePaginationIterator {
249
282
  yield page;
250
283
  iterateItemCount += page.items.length;
251
284
  // Limit reached stopping to iterate
252
- if ((this.limit && iterateItemCount >= this.limit) || !page.nextCursor)
285
+ if ((this.#limit && iterateItemCount >= this.#limit) || !page.nextCursor)
253
286
  return;
254
287
  nextCursor = page.nextCursor;
255
- nextExclusiveStartId = undefined; // see comment above - delete it for any page after the first one, and paginate with cursor
256
288
  }
257
289
  }
258
290
  }
@@ -266,6 +298,16 @@ export const paginationOptionsShape = {
266
298
  offset: z.number().min(0).optional(),
267
299
  chunkSize: z.number().positive().optional(),
268
300
  };
301
+ /**
302
+ * Key under which a page of dataset items carries the number of rows the API scanned to produce it, as a
303
+ * non-enumerable property that stays out of the page's public shape. The API applies `offset` and `limit` to the
304
+ * dataset's rows first and shapes the result afterwards: filters (`clean`, `skipEmpty`, `skipHidden`) drop items and
305
+ * `unwind` splits or drops them, so `items.length` can land on either side of the rows scanned. The
306
+ * `x-apify-pagination-count` header reports the scanned number, and the offset iterator advances and terminates by it
307
+ * alone. A page without the property falls back to `items.length`.
308
+ * @internal
309
+ */
310
+ export const SCANNED_COUNT = Symbol('scannedCount');
269
311
  export function cast(input) {
270
312
  return input;
271
313
  }
@@ -292,15 +334,6 @@ export function applyQueryParamsToUrl(url, options) {
292
334
  }
293
335
  return url;
294
336
  }
295
- /**
296
- * Builds a `[check, message]` pair to spread into `.refine()`, asserting that at most one of `keys`
297
- * is present. Pass the options interface as `T`, so that a misspelled key is a type error.
298
- * @internal
299
- */
300
- export const mutuallyExclusive = (...keys) => [
301
- (value) => keys.filter((key) => typeof value[key] !== 'undefined').length <= 1,
302
- `At most one of the following fields is allowed: ${keys.join(', ')}`,
303
- ];
304
337
  const pathSegmentSchema = z
305
338
  .string()
306
339
  .nonempty()
@@ -322,3 +355,24 @@ export function toPathSegment(value) {
322
355
  export function toPath(path) {
323
356
  return Array.isArray(path) ? path.map(toPathSegment).join('/') : path;
324
357
  }
358
+ /**
359
+ * Resolves after `millis`, or right away once `signal` aborts.
360
+ * @internal
361
+ */
362
+ export async function sleep(millis, signal) {
363
+ return new Promise((resolve) => {
364
+ if (signal?.aborted) {
365
+ resolve();
366
+ return;
367
+ }
368
+ const onAbort = () => {
369
+ clearTimeout(timer);
370
+ resolve();
371
+ };
372
+ const timer = setTimeout(() => {
373
+ signal?.removeEventListener('abort', onAbort);
374
+ resolve();
375
+ }, millis);
376
+ signal?.addEventListener('abort', onAbort, { once: true });
377
+ });
378
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-client",
3
- "version": "3.0.0-beta.4",
3
+ "version": "3.0.0-beta.40",
4
4
  "description": "Apify API client for JavaScript",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -10,6 +10,12 @@
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-03T111309Z"
62
+ "version": "v2-2026-10-01T153946Z"
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-beta.199",
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": "^24.19.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.71.0",
95
+ "oxlint": "1.85.0",
96
+ "oxlint-tsgolint": "7.0.2003",
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.8.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'",
@@ -126,6 +129,6 @@
126
129
  "build:node": "tsc",
127
130
  "build:browser": "rsbuild build",
128
131
  "spec:fetch": "node scripts/openapi_spec.mts fetch",
129
- "generate:types": "node scripts/openapi_spec.mts fetch && node scripts/generate_types.mts && node scripts/openapi_spec.mts record-version"
132
+ "generate:models": "node scripts/openapi_spec.mts fetch && node scripts/generate_types.mts && node scripts/generate_schemas.mts && node scripts/openapi_spec.mts record-version"
130
133
  }
131
134
  }