Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2406ece
chore!: require Node.js 22 or newer (#984)
vdusek Aug 3, 2026
9828c5d
test: add integration test suite
vdusek Aug 10, 2026
a5d98d6
refactor!: replace ow with zod for argument validation
vdusek Jul 30, 2026
ae82212
fix: align isBuffer with ow's semantics and tighten validation typing
vdusek Jul 30, 2026
2191554
chore(deps): require zod v4
vdusek Jul 31, 2026
eb8a2a2
docs: tighten comments added by the zod migration
vdusek Jul 31, 2026
e94769f
fix: report every failed union arm in ArgumentValidationError
vdusek Jul 31, 2026
acfe409
perf: validate pushed dataset items without copying them
vdusek Jul 31, 2026
c9f8af0
docs: document ArgumentValidationError in the error handling guide
vdusek Jul 31, 2026
b29e000
test: cover strict option schemas and mutually exclusive options
vdusek Jul 31, 2026
c0d9fff
fix: reject symbols and bigints in setRecord value validation
B4nan Aug 3, 2026
9963e87
refactor: share one passthrough object schema across resource clients
B4nan Aug 3, 2026
4902dd5
fix: render empty strings visibly in validation error messages
B4nan Aug 3, 2026
72fd6a4
test: cover the isBuffer and isStream helpers directly
B4nan Aug 3, 2026
671ea6e
refactor: use z.strictObject, z.looseObject and z.enum instead of dep…
vdusek Aug 4, 2026
c44cd64
perf: tree-shake and minify the browser bundle
vdusek Aug 4, 2026
062b803
fix!: accept chunkSize on every paginating list method
vdusek Aug 4, 2026
98dc316
fix: name the constraint when zod rejects Infinity, NaN or an invalid…
vdusek Aug 4, 2026
0e7cee7
perf: validate request batches without copying them
vdusek Aug 4, 2026
8728069
docs: tighten the comments added by the zod migration
vdusek Aug 4, 2026
9ebf5b8
docs: drop fix backstory from paginationOptionsShape comment
B4nan Aug 4, 2026
9f26108
docs: add upgrading-to-v3 guide covering the ow-to-zod validation change
vdusek Aug 5, 2026
a1ae2c6
test: address review feedback on the integration test suite
vdusek Aug 18, 2026
f405369
refactor: address review findings from the zod migration
vdusek Aug 18, 2026
d7e4835
test: do not require store results for the unused pricing model
vdusek Aug 18, 2026
cc817bc
Merge remote-tracking branch 'origin/test/integration-suite' into fea…
vdusek Aug 18, 2026
3b9370e
feat: reject function values in setRecord() validation
B4nan Aug 18, 2026
3bdf666
Merge branch 'v3' into feat/replace-ow-with-zod
vdusek Aug 19, 2026
bdd8736
refactor: align argument validation with Crawlee's parseArgument
vdusek Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/02_concepts/02_error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,27 @@ try {
}
```

## Invalid arguments

Before sending a request, the client validates the arguments you passed. When a value doesn't match the expected shape, the client throws an <ApiLink to="class/ArgumentValidationError">`ArgumentValidationError`</ApiLink> without reaching the API. Its `message` names the offending field and the value it received. For programmatic inspection, `issues` carries the structured [zod](https://zod.dev) issues and `cause` carries the original `ZodError`.

```js
import { ApifyClient, ArgumentValidationError } from 'apify-client';

const client = new ApifyClient({ token: 'MY-APIFY-TOKEN' });

try {
await client.dataset('my-dataset').listItems({ limit: 'ten' });
} catch (error) {
if (error instanceof ArgumentValidationError) {
// Invalid input: expected number, received string at `limit`, got `ten`
console.log(error.message);
// [{ code: 'invalid_type', expected: 'number', path: ['limit'], ... }]
console.log(error.issues);
}
}
```

## Retries with exponential backoff

The client automatically retries requests that fail due to network errors, Apify API internal errors (HTTP 500+), or rate limit errors (HTTP 429). By default, the client retries up to 8 times with exponential backoff starting at 500ms.
Expand Down
76 changes: 76 additions & 0 deletions docs/04_upgrading/upgrading_v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
id: upgrading-to-v3
title: Upgrading to v3
sidebar_label: Upgrading to v3
description: 'Breaking changes to be aware of when upgrading to version 3 of the Apify API client for JavaScript.'
---

import ApiLink from '@theme/ApiLink';

This page summarizes the breaking changes when upgrading from v2 to v3 of `apify-client`.

## Argument validation switched from `ow` to `zod`

The client now validates the arguments you pass with [zod](https://zod.dev) instead of `ow`. This changes what gets thrown for invalid arguments, and tightens a few gaps `ow` used to let through silently.

### A new error type

Invalid arguments now throw an <ApiLink to="class/ArgumentValidationError">`ArgumentValidationError`</ApiLink> (exported from `apify-client`), not `ow`'s `ArgumentError`. Its message is a plain, human-readable sentence naming the offending field and the value it received, rather than `ow`'s JSON dump:

```diff
- Expected property string `countryCode` to match `/^[A-Z]{2}$/`, got `CZE` in object // v2 (ow)
+ Invalid string: must match pattern /^[A-Z]{2}$/ at `countryCode`, got `CZE` // v3 (zod)
```

The structured zod issues are available on `issues`, and the original `ZodError` on `cause`:

```js
import { ApifyClient, ArgumentValidationError } from 'apify-client';

const client = new ApifyClient({ token: 'MY-APIFY-TOKEN' });

try {
await client.dataset('my-dataset').listItems({ limit: 'ten' });
} catch (error) {
if (error instanceof ArgumentValidationError) {
console.log(error.message); // Invalid input: expected number, received string at `limit`, got `ten`
console.log(error.issues); // [{ code: 'invalid_type', expected: 'number', path: ['limit'], ... }]
}
}
```

If you were matching on `ow`'s `ArgumentError`, switch to `ArgumentValidationError`. If you were parsing the old message text, use `issues` instead.

### Arrays and functions no longer pass where a plain object is expected

`ow`'s object check let arrays and functions through wherever a plain object was expected. Zod's does not, so passing one now throws instead of reaching the API with a nonsensical body. This affects `update()` / `create()` fields, `TaskClient.start()` / `call()` input, the storage `schema` option, `DatasetClient.pushItems()` items, and `RequestQueueClient.addRequest()` / `batchAddRequests()` requests.

```js
// Now throws: Invalid input: expected object, received array
await client.actor('my-actor').update([{ name: 'my-actor' }]);
```

`Date`, `Map`, `Set` and other class instances still pass as objects, same as under `ow`.

### `Infinity` and invalid `Date`s are now rejected

`ow` only checked the type, so `Infinity` passed as a number and an invalid `Date` passed as a date. Zod additionally requires a *finite* number and a *valid* date, so both now throw:

```js
// Now throws: Invalid input: expected a finite number at `timeout`, got `Infinity`
await client.actor('my-actor').call(undefined, { timeout: Infinity });

// Now throws: Invalid input: expected a valid date at `startedBefore`
// Invalid input: expected string, received Date at `startedBefore`
await client.actor('my-actor').runs().list({ startedBefore: new Date('nonsense') });
```

The second example reports a line per arm, because `startedBefore` accepts either a `Date` or a string.

This affects numeric options such as `waitSecs`, `timeout` and `memory`, and date options such as `startedBefore` / `startedAfter`. `KeyValueStoreClient.setRecord()` rejects `NaN` and `Infinity` as a record value too, since `JSON.stringify()` turns both into `null`.

### A few always-rejected options are gone from the types

Some options were declared in the TypeScript types but always rejected by the client's own validation before a request was ever sent: `chunkSize` on `DatasetClient.downloadItems()` and `createItemsPublicUrl()`, and `signature` on `createItemsPublicUrl()` and `createKeysPublicUrl()`. These are no longer part of the option types, so passing them is now a compile-time error instead of a runtime throw.

The reverse also happened: `chunkSize` now works on every paginating `list()` method. In v2 only `DatasetClient.listItems()` accepted it - everywhere else it type-checked and then threw.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@
"async-retry": "^1.3.3",
"axios": "^1.16.0",
"content-type": "^1.0.5",
"ow": "^0.28.2",
"proxy-agent": "^6.5.0",
"tslib": "^2.5.0",
"type-fest": "^4.0.0"
"type-fest": "^4.0.0",
"zod": "^4.0.0"
},
"devDependencies": {
"@apify/oxlint-config": "^0.3.0",
Expand Down
11 changes: 8 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 17 additions & 4 deletions rsbuild.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';

import { version } from './package.json';

const MAX_BUNDLE_BYTES = 320 * 1024;

const nodeOnlyModules = /^proxy-agent$/;
const unusedInBrowserBuiltins = ['os', 'zlib', 'util'];
const builtinAliases = Object.fromEntries(
Expand Down Expand Up @@ -33,7 +35,11 @@ export default defineConfig({
minify: {
jsOptions: {
minimizerOptions: {
mangle: false,
// Class names are load-bearing: `ApifyApiError` and `InvalidResponseBodyError` take
// their `name` from `constructor.name`, and `ResourceClient.waitForFinish()` parses
// the client name out of it.
compress: { keep_classnames: true },
mangle: { keep_classnames: true },
},
},
},
Expand All @@ -52,10 +58,17 @@ export default defineConfig({
};
config.optimization = {
...config.optimization,
providedExports: false,
usedExports: false,
splitChunks: false,
minimize: false,
};
// A regression guard, not a target: the bundle sits at ~288 kB, so this only fails the
// build on an unnoticed jump. A `zod` minor is the likeliest cause, since it is a runtime
// dependency on a caret range - bumping this constant is the expected response.
config.performance = {
hints: 'error',
maxAssetSize: MAX_BUNDLE_BYTES,
maxEntrypointSize: MAX_BUNDLE_BYTES,
// The source map is many times the size of the bundle and ships separately.
assetFilter: (filename) => filename === 'bundle.js',
};
config.plugins = [...(config.plugins ?? []), new rspack.IgnorePlugin({ resourceRegExp: nodeOnlyModules })];
config.resolve = {
Expand Down
Loading
Loading