Skip to content

refactor!: package split + issue #8 review fixes; sync spec to v2-2026-07-20T094852Z#10

Open
Pijukatel wants to merge 10 commits into
masterfrom
claude/vibrant-edison-vq1jx6
Open

refactor!: package split + issue #8 review fixes; sync spec to v2-2026-07-20T094852Z#10
Pijukatel wants to merge 10 commits into
masterfrom
claude/vibrant-edison-vq1jx6

Conversation

@Pijukatel

@Pijukatel Pijukatel commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Syncs the Java client to Apify OpenAPI spec v2-2026-07-20T094852Z and releases 0.4.0, applying the structural changes from the expert review in #8. This is a breaking release (explicitly authorized).

Structure & public API

  • Splits the single com.apify.client package into resource-scoped sub-packages (.actor, .build, .run, .dataset, .keyvalue, .requestqueue, .task, .schedule, .webhook, .user, .store, .log, .http). A module-info.java exports only these and keeps implementation plumbing in a non-exported com.apify.client.internal package.
  • Renames the replaceable HTTP transport: HttpBackendHttpTransport, DefaultHttpBackendDefaultHttpTransport, sendStreamingsendStreamingResponse.
  • Reworks the exception hierarchy under a common ApifyClientException base, with ApifyApiException and a transport-agnostic ApifyTransportException / HttpTimeoutException.
  • Centralizes collection path segments in ApiPaths; makes ResourceContext and the storage clients immutable. ValidateInputOptions lives in com.apify.client.actor, alongside its only consumer (ActorClient.validateInput).

Behavior & correctness

  • RequestQueueClient.batchAddRequests chunks by both request count and cumulative payload byte size (so a batch of large requests can't 413), and never throws on a hard 4xx or a transport-level failure (timeout, connection error) — failed requests are returned via BatchAddResult.getUnprocessedRequests(), matching the reference client's contract.
  • Typed return values / getters replace raw JsonNode / getExtra() access across the request-queue lock operations and the Schedule / Webhook / Task / ActorRun models; every list-returning DTO getter tolerates an explicit JSON null without throwing.
  • Adds log-streaming call(..., ActorCallOptions / TaskCallOptions, ...) overloads that stream the run's log during the wait (matching the reference client); the plain call(..., *StartOptions, ...) overload is unchanged.
  • RunClient.metamorph validates targetActorId; ApifyClientBuilder validates inputs (rejects a zero/negative timeout at build time); brotli compression is opt-in; logging goes through SLF4J.

Docs & tests

  • Full documentation pass; the "official, but experimental — AI-generated/-maintained" disclaimer appears exactly once (top-level README); every model's field list is complete and cross-referenced with its package.
  • Expanded live-integration and offline coverage, including last-run- and run-scoped nested storage access and concurrency-safe assertions against the shared apify/hello-world store Actor. mvn test (unit + live integration + ExamplesTest + DocSnippetsTest): 186/186 passing.

Review points from #8 intentionally not applied

  • ApifyClient.setStatusMessage(...) is kept — the reference JS client has it, so removing it would break the required convenience-method parity.
  • ApifyApiException stays a RuntimeException-rooted type (parity with the JS error model, which is a plain unchecked error).
  • A Jackson 2→3 upgrade and an async/reactive transport rewrite are out of scope for this PR.

Test plan

  • mvn spotless:check
  • mvn compile
  • mvn spotbugs:check
  • mvn test (offline unit + live integration + ExamplesTest + DocSnippetsTest) — 186/186 passing

…6-07-20T094852Z

Addresses the expert review at #8: splits the single
com.apify.client package into resource-scoped sub-packages, renames the
replaceable HTTP transport (HttpBackend -> HttpTransport), reworks the
exception hierarchy (new ApifyClientException base, public
ApifyTransportException, backend-agnostic HttpTimeoutException), makes
ResourceContext/DatasetClient/KeyValueStoreClient immutable, centralizes
resource path segments into ApiPaths, removes setStatusMessage() and the
public getUserAgent()/getApiBaseUrl(), makes brotli opt-in and adds slf4j
logging. Several review suggestions are explicitly skipped where they would
conflict with the reference-client naming/behavior parity requirement (see
notes.md); documented with rationale. Bumps to 0.4.0 (breaking) and syncs
Version.API_SPEC_VERSION to v2-2026-07-20T094852Z (response-only spec delta,
no behavior change needed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
claude added 3 commits July 20, 2026 15:13
…estore setStatusMessage

Fixes the headline correctness bug (ApifyApiException now extends ApifyClientException) and a
PaginationList NPE on "items": null. Hides implementation plumbing behind a non-exported
com.apify.client.internal package with a module-info.java, restores ApifyClient.setStatusMessage
(the prior removal was based on a false claim about the reference client), and removes the
duplicated AI-disclaimer from the 10 docs pages. Also covers the accumulated code-quality, docs,
changelog, test-coverage, parity-gap and issue #8 follow-ups from the review; see notes.md
"Implementer — loop 2" for the itemized list and the explicit skip-with-justification entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuTYpvY6WkVvmsXzupzEk9
…ns, log-streaming call(), DRY collection clients

Closes every item from the loop-2 review's remaining-actionable list: batchAddRequests now also
splits by payload byte size (not just count) to avoid 413s; RequestQueueClient's lock/list
operations return typed models instead of raw JsonNode; Schedule/Webhook/Task/ActorRun gained
typed getters for fields previously reachable only via getExtra(); new ActorCallOptions/
TaskCallOptions call() overloads stream the run's log by default, matching the reference client;
extracted AbstractCollectionClient to de-duplicate the 8 collection clients' list/iterate
boilerplate; plus the requested docs completions and style nits. See notes.md "Implementer — loop
3" in apify-client-orchestration for the itemized list and test evidence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuTYpvY6WkVvmsXzupzEk9
…ver-throws, parity getters, call() DRY

Closes every item from the loop-3 review's residual list: removed the duplicated AI/experimental
disclaimer from ApifyClient's class javadoc (README-only now); RequestQueueClient.batchAddRequests
no longer throws on a non-retryable 4xx, matching the reference client's never-throws contract
(unprocessed requests reported via BatchAddResult instead); added offline mock-backend coverage for
RunClient.metamorph/reboot and corrected the stale ActorRunIntegrationTest comment; added the
remaining typed getters (ActorRun.usageTotalUsd/buildNumber/exitCode/isContainerServerReady/
gitBranchName/storageIds, Webhook.shouldInterpolateStrings/lastDispatch); extracted
StartOptionsLike/CallOptionsLike + a RunStartSupport helper so ActorClient/TaskClient's start/call
machinery stops duplicating ~185 lines; ApifyClientBuilder.timeout now rejects zero/negative
durations; RequestQueueRequest.userData is defensively deep-copied; RequestQueueClient's cursor
iterator now trims an overshot page to totalLimit; tightened spotbugs-exclude.xml to entries that
actually reproduce a finding; plus the requested docs completions and CI permissions block. See
notes.md "Implementer — loop 4" in apify-client-orchestration for the itemized list and test
evidence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuTYpvY6WkVvmsXzupzEk9
claude added 6 commits July 21, 2026 07:23
…ocs contradictions, uniqueKey caveat

Closes the loop-4 review's residual MAJORs and nits:
- N1: RunStartSupport now takes Consumer<QueryParams>/plain values instead of the public
  CallOptionsLike/StartOptionsLike marker interfaces, so ActorClient/TaskClient can keep
  apply()/contentTypeOrDefault()/toStartOptions()/logStreamingEnabledValue()/logOptionsValue()
  package-private on ActorStartOptions/TaskStartOptions/ActorCallOptions/TaskCallOptions instead of
  leaking them (and the internal QueryParams type) into the options builders' public API.
- N2: reconciled the examples.md contradiction across README.md/docs/README.md/docs/examples.md —
  all three now agree that the fragments are fragments and the runnable programs live under
  src/test/java/com/apify/client/examples/.
- N3: added the missing WebhookLastDispatch import to docs/README.md's package table.
- N4: added an offline mock-backend test for ApifyClient.setStatusMessage's ACTOR_RUN_ID-unset
  IllegalStateException path (previously zero coverage); documented why the ACTOR_RUN_ID-set path
  isn't offline- or live-testable, mirroring the metamorph/reboot/charge convention.
- N5: added a javadoc caveat on batchAddRequests/RequestQueueRequest.getUniqueKey() about
  uniqueKey-based retry reconciliation falsely reporting a succeeded request as unprocessed.
- Nits: isShouldInterpolateStrings() -> getShouldInterpolateStrings() (field name unchanged, so no
  wire-format effect); named the unused RequestQueueClient catch binding `ignored`; fixed the
  "one raw-JSON field" framing in docs/README.md; clarified the request-queue getOrCreate overload
  gap in docs/storages.md; documented SetStatusMessageOptions's primitive boolean; trimmed
  boilerplate javadoc on trivial RequestQueueRequest setters; documented
  DefaultHttpTransport(Duration) and evened out import disclosure in README fragments; strengthened
  the nested-webhook test comment to point at the real multi-item iteration coverage.

mvn spotless:check/compile/spotbugs:check/test all green: 183/183 tests passing (unit + live
integration + ExamplesTest + DocSnippetsTest), 0 failures, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuTYpvY6WkVvmsXzupzEk9
…setter trim

Addresses the two concrete findings from this loop's coding-style review:
- The near-identical "package-private on purpose" paragraph was copy-pasted across
  ActorStartOptions/ActorCallOptions/TaskStartOptions/TaskCallOptions's class javadoc. Condensed
  each to a one-line pointer at RunStartSupport, whose own javadoc (now with @param docs on
  call/callWithLogStreaming) carries the full rationale once.
- RequestQueueRequest's setter-javadoc trim was inconsistent: setId/setUrl/setMethod/setPayload had
  their boilerplate one-liner removed, but setHandledAt/setRetryCount/setLoadedUrl (equally trivial
  restatements of their getters) did not. Trimmed those three too for consistency; setUserData/
  setHeaders/setNoRetry/setUniqueKey/setErrorMessages are untouched (still the mandated
  keep-as-informative set).

mvn spotless:check/compile/spotbugs:check/test all green: 183/183 tests passing, 0 failures, 0
errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuTYpvY6WkVvmsXzupzEk9
…doc gaps

Addresses the second independent /review-client pass's residual findings on top of f705ae6:

- P1 (CI, merge-blocking): ActorRunIntegrationTest.streamedLogRedirection failed in CI on f705ae6
  (the same live race as loop 1/2 — a live-tail stream opened before the run finishes can reach EOF
  a hair before the final log bytes are flushed to that connection). Fix: after the bounded
  catch-up window, if the run produced a log but the first stream still came up empty, retry once
  with a brand-new StreamedLog opened strictly after the run is already finished — a GET against a
  static, fully-persisted log rather than a live tail, so it cannot race the run's own writer.
  Verified green 3x locally against the live API.
- P2: fixed a factually wrong RunStartSupport javadoc claim that ActorStartOptions/TaskStartOptions/
  ActorCallOptions/TaskCallOptions "stay package-private" — those classes are public (they must be,
  as public method parameter/return types); only 5 of their accessor methods are package-private.
- P5: AbstractCollectionClient.list(options) NPE'd on null options while iterate(options) tolerated
  it — now consistent. BatchAddResult.getProcessedRequests()/getUnprocessedRequests()/merge() now
  null-coalesce, matching PaginationList's existing pattern, so an explicit
  "processedRequests": null / "unprocessedRequests": null API response can no longer NPE.
- P7 (docs): documented that ValidateInputOptions lives in com.apify.client.task despite
  configuring an ActorClient method; added java.time.Instant to the standard-import list; noted
  that setStatusMessage/UserClient limits methods/ApifyClientBuilder setters throw plain JDK
  exceptions outside the ApifyClientException hierarchy; clarified waitForFinish/call's behavior
  when the wait budget expires before the run reaches a terminal state.

mvn spotless:check/compile/spotbugs:check/test all green: 183/183 tests passing, 0 failures, 0
errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuTYpvY6WkVvmsXzupzEk9
…d-Actor race, DTO null-guards, RunClient.metamorph validation, package move, docs

Addresses every item from the loop-6 /review-client verdict: RequestQueueClient.batchAddChunkWithRetries
now catches ApifyClientException (not just ApifyApiException) so transport failures also stay within
the never-throws contract; ActorRunIntegrationTest#lastRunAccess no longer asserts run identity against
the shared apify/hello-world Actor (only presence/status/origin); added last-run- and run-scoped nested
storage GET coverage; null-coalesced every flagged list-returning DTO getter; moved ValidateInputOptions
to com.apify.client.actor next to its only consumer; RunClient.metamorph validates targetActorId; various
docs completeness, comment, and DRY nits. See notes.md "Implementer — loop 7" in apify-client-orchestration
for the itemized list and test evidence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuTYpvY6WkVvmsXzupzEk9
- RequestQueueClient: null-coalesce BatchDeleteResult's list getters and
  RequestQueueRequest.getErrorMessages() to avoid NPEs on an explicit JSON
  null, mirroring BatchAddResult.
- RunClient.metamorph: normalize targetActorId via ResourceContext.toSafeId
  before sending (matches the reference client's _toSafeId); update the
  offline mock test's expected query param.
- RunClient.metamorph/resurrect and ActorClient.build: throw a clear
  IllegalArgumentException on a null required options argument instead of an
  NPE deep inside the method.
- RunClient's default log-redirection prefix now uses the resolved run's real
  id instead of the field id (which is the literal "last" for a runs/last
  accessor).
- Box ActorRunOptions.timeoutSecs/memoryMbytes/diskMbytes (Long) so an unset
  value is distinguishable from 0, matching the boxed sibling fields.
Flesh out response DTOs to match the JS reference's typed interfaces,
resolving the recurring thin-DTO review category:

- WebhookDispatch: typed status, eventType, userId, createdAt, calls
  (WebhookDispatchCall), webhook (WebhookDispatchWebhookInfo), eventData
  (WebhookDispatchEventData).
- Actor: wire in getActorStandby() (ActorStandby, now also carrying the
  Actor-only isEnabled field), plus stats (ActorStats), versions,
  pricingInfos, defaultRunOptions (ActorDefaultRunOptions), taggedBuilds,
  actorPermissionLevel, categories, isDeprecated, deploymentKey,
  seoTitle/seoDescription.
- Build: userId, meta (BuildMeta), stats (BuildStats), options
  (BuildOptions), usage/usageUsd/usageTotalUsd (BuildUsage).
- User: profile (UserProfile), email, proxy (UserProxy), plan (UserPlan),
  effectivePlatformFeatures, createdAt, isPaying.
- ActorStoreListItem: description, stats, currentPricingInfo (PricingInfo),
  pictureUrl, userPictureUrl, url, readmeSummary.

Task/ActorRun were already complete from an earlier loop; verified against
the JS reference and left unchanged. Every new field name/type was checked
against apify-client-js's resource_clients interfaces before modelling.
getExtra() is kept for forward-compat on every touched model. Docs field
lists updated to match (actors.md, builds.md, misc.md, webhooks.md,
examples.md); the GetAccount example and its doc snippet now read the typed
getEmail() instead of reaching into getExtra().

spotbugs-exclude.xml: added the new pure-DTO classes to the existing
UWF_UNWRITTEN_FIELD allow-list (same false-positive rationale as the
pre-existing entries); rewrote the "historical note" to state only the
current timeless rationale, dropping the loop-numbered narrative.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants