Skip to content

fix!: throw on ambiguous 404s instead of resolving to undefined - #1042

Merged
vdusek merged 7 commits into
v3from
fix/ambiguous-404-throws
Sep 10, 2026
Merged

fix!: throw on ambiguous 404s instead of resolving to undefined#1042
vdusek merged 7 commits into
v3from
fix/ambiguous-404-throws

Conversation

@vdusek

@vdusek vdusek commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

A 404 collapsed into undefined everywhere, including where it can't be pinned to one resource: run.dataset().get() couldn't tell a missing run from a missing dataset. Those calls now throw.

  • Chained clients without an ID (run.dataset(), run.keyValueStore(), run.requestQueue(), run.log(), build.log()) throw from get(), delete(), log().get() and log().stream(). A new catchNotFoundForResourceOrThrow(err, this.id) keys on the ID, so ID-addressed clients still resolve to undefined.
  • Fixed sub-paths throw and drop | undefined: getStatistics(), monthlyUsage(), limits(), getLog(), getInput(), test().
  • Unchanged: getRecord(), getRequest(), recordExists() and lastRun().
  • version(), build() and envVar() reject an empty string, which used to address the collection.
  • UserClient.get() is now Promise<User | undefined>, matching what it always returned.
  • getStreamedLog() on a missing run warns and stops.

Merging v3 also moved LogClient.stream() under the same rule. #1041 made catchNotFoundOrThrow() a plain instanceof NotFoundError check, so a streamed 404 matches it and client.log(id).stream() resolves to undefined the way get() does. #1043 raised that flip as an open question; the unusable error body it reports is still open.

Mirrors apify/apify-client-python#755.

Closes #1030

✍️ Drafted by Claude Code

@vdusek vdusek added the t-tooling Issues with this label are in the ownership of the tooling team. label Sep 8, 2026
@vdusek vdusek self-assigned this Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

⚠️ There are broken links in the documentation.

See more at https://github.com/apify/apify-client-js/actions/runs/34445566589#summary-102769495031

A 404 collapses into `undefined` only where it names one resource: a direct
`get()` or `delete()` on a client created with an ID, and the record lookups
`getRecord()` and `getRequest()`. Where it could mean either the parent or
the sub-resource is missing, the `ApifyApiError` now propagates.

- Chained clients without an ID (`run.dataset()`, `run.keyValueStore()`,
  `run.requestQueue()`, `run.log()`, `build.log()`): `get()`, `delete()`,
  `log().get()` and `log().stream()` throw. `client.log(id)` still resolves
  to `undefined`.
- Singleton sub-path endpoints: `DatasetClient.getStatistics()`,
  `UserClient.monthlyUsage()`, `UserClient.limits()`,
  `ScheduleClient.getLog()`, `TaskClient.getInput()` and
  `WebhookClient.test()` throw, and their return types drop `| undefined`.
- `StreamedLog` logs a warning and stops when the run log answers 404,
  instead of leaving its background task rejected.

The mock server's text handler now sends the status code it derives from
the resource ID, so `run('404').log().get()` can be exercised.

Mirrors apify/apify-client-python#755. Closes #1030.

BREAKING CHANGE: `run.dataset()`, `run.keyValueStore()`,
`run.requestQueue()`, `run.log()` and `build.log()` throw an `ApifyApiError`
on a 404 instead of resolving to `undefined`. `DatasetClient.getStatistics()`,
`UserClient.monthlyUsage()`, `UserClient.limits()`, `ScheduleClient.getLog()`,
`TaskClient.getInput()` and `WebhookClient.test()` throw on a 404 and no
longer include `undefined` in their return types.
@vdusek
vdusek force-pushed the fix/ambiguous-404-throws branch from a7dff98 to 0225c15 Compare September 8, 2026 10:24
@vdusek
vdusek marked this pull request as ready for review September 8, 2026 12:25
@vdusek
vdusek requested a review from szaganek as a code owner September 8, 2026 12:25
@vdusek
vdusek requested a review from janbuchar September 8, 2026 12:25
Comment thread src/resource_clients/run.ts Outdated
Conflicts were in the error-handling guide, where both sides added a section,
and in an integration assertion that v3 tightened.

The merge also changes `LogClient.stream()`. On v3 `catchNotFoundOrThrow()` is a
plain `instanceof NotFoundError` check, so a streamed 404 now matches and an
ID-addressed log resolves to `undefined` like `get()` does. The docstring, the
upgrade guide and the unit test follow that.
Both conflicts were import lists that each side extended, resolved as the union.
@vdusek
vdusek merged commit aabe9f2 into v3 Sep 10, 2026
8 checks passed
@vdusek
vdusek deleted the fix/ambiguous-404-throws branch September 10, 2026 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-tooling Issues with this label are in the ownership of the tooling team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants