Skip to content

Refine API Client architecture and request lifecycles - #11

Closed
binaryfire wants to merge 20 commits into
0.4from
audit/api-client-correctness-ergonomics
Closed

Refine API Client architecture and request lifecycles#11
binaryfire wants to merge 20 commits into
0.4from
audit/api-client-correctness-ergonomics

Conversation

@binaryfire

@binaryfire binaryfire commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR rebuilds API Client around reusable integration definitions and fresh pending operations. The public surface now follows the same fluent conventions as the HTTP client while adding the API-specific pieces integrations need: typed resources, operation context, and request and response middleware.

The change also fixes the shared HTTP request-state defects exposed by the API Client work, makes split-package dependency ownership accurate, and adds first-party documentation.

API Client design

API client classes now hold only boot-stable integration defaults. Each call creates a fresh pending request, applies the client defaults through configurePendingRequest, and keeps all mutable request state local to that operation.

The new surface:

  • forwards normal HTTP client configuration methods without losing fluent chains or value-returning results;
  • separates API middleware from HTTP and Guzzle middleware with explicit append, replace, and disable methods;
  • resolves middleware through Pipeline and the container so transient, scoped, singleton, and worker-reused lifetimes behave normally;
  • propagates operation-local context through request middleware, the final retry attempt, and response middleware;
  • installs one retry-aware bridge instead of accumulating callbacks when a pending request is reused;
  • clears transient active-request state on every success and failure path;
  • keeps typed resource inference across client calls, explicit pending requests, terminals, and withResource narrowing.

Typed data objects remain available for client configuration, but they are optional constructor dependencies rather than a base-class requirement.

Request and response correctness

API request mutation now has one coherent owner for structured data, raw bodies, media types, and framing. JSON and form mutations keep logical data, encoded bytes, content type, and content length synchronized. Raw and multipart bodies fail at the conversion boundary instead of producing stale or invalid request state. Replacement streams are seekable and preserve transfer-encoding rules.

API response reconstruction preserves cookies, transfer metadata, decoder state, flags, and exception settings. Replacing a response body invalidates the old decoded value. Resource array conversion now distinguishes arrays, empty bodies, JSON null, malformed JSON, scalar JSON, and custom decoders while leaving raw response access available.

The shared HTTP client changes:

  • normalize JSON and form media types without losing legal parameters;
  • preserve exact logical payload values until middleware replaces the prepared body;
  • invalidate stale logical data before later callbacks, fakes, recorders, and API middleware observe the request;
  • gate body-identity tracking behind configured Guzzle middleware and avoid reading body bytes;
  • preserve numeric query keys and omitted GET and HEAD query arguments;
  • normalize supported JsonSerializable query values recursively;
  • report malformed and scalar JSON through their correct exception boundaries;
  • accept one query key or a key list when removing query parameters.

Telescope now uses structured request metadata only while it remains authoritative and falls back to its bounded PSR-body parser for raw or replaced bodies.

Explicit transport boundaries

API Client remains synchronous. async(true) is rejected before dispatch instead of performing work that cannot produce a valid API resource. Replacing the complete Guzzle client is also rejected because it bypasses the required API bridge. setHandler remains the supported low-level transport seam.

HTTP errors remain non-throwing by default, matching the HTTP client. Applications may opt into exception behavior with throw.

Package ownership

The split manifests now declare the Guzzle, PSR HTTP, Flysystem, URI, CommonMark, Monolog, and promises packages imported directly by their source. Optional search clients and Faker are suggestions only where they enable documented features. Reverb no longer declares an unused API Client dependency.

Metadata tests compare split constraints with the root manifest instead of copying version literals that drift during routine dependency updates.

Performance

The design removes worker-shared middleware mutation, package-local middleware caching, option staging, callback growth, and stale-state repair paths. Operation context uses PHP copy-on-write arrays. Stateless unbound middleware continues to use normal worker-lifetime container reuse. Prepared-body tracking exists only when Guzzle middleware is configured and compares stream identity without parsing the body.

No request path gains locks, serialization, polling, extra network calls, unbounded caches, or retained request graphs.

Documentation and validation

A new Laravel-style API Client guide covers integration classes, typed configuration, pending requests, context, resources, errors, middleware, body mutation, transport customization, and HTTP fakes. The package README points to this single documentation source.

Focused package and shared-owner suites, strict Composer validation, formatting, static analysis, the complete parallel suite, Testbench package mode, documentation examples, and final diff checks are green.

For more details, see: docs/plans/2026-08-09-2105-api-client-correctness-laravel-ergonomics-and-lifecycles.md.

Summary by CodeRabbit

  • New Features
    • Added a more flexible API client with synchronous requests, query handling, middleware, context, resources, and response conversion.
    • Added structured request data, raw body replacement, array-based responses, and readable JSON output.
  • Bug Fixes
    • Improved request/response state handling, query normalization, body synchronization, retry isolation, and sensitive token protection.
    • Corrected HTTP payload parsing and media-type handling.
  • Documentation
    • Added comprehensive API client guidance and updated documentation navigation.
  • Chores
    • Aligned package dependencies and optional integration suggestions.

Make API client instances reusable integration prototypes that create a fresh pending request for each operation.

Separate API middleware from the underlying HTTP middleware surface, resolve middleware through Pipeline and the container, and use decorated forwarding so fluent and value-returning HTTP methods remain truthful.

Install one retry-aware bridge, keep context and active request state operation-local, preserve configured-query omission semantics, reject unsupported async and custom-client paths before transport, and clear transient state on every exit.
Give API request middleware one coherent model for structured and raw data.

Keep logical data, encoded bytes, media type, and framing synchronized after every mutation. Preserve GET and HEAD query ownership, dot-notation removal, seekable PSR streams, transfer encoding, custom JSON media types, and exact content lengths.

Use throwing JSON encoding, reject unrepresentable conversions at the API boundary, and preserve wrapper attributes and decoded state during reconstruction.
Reconstruct API responses without losing cookies, transfer metadata, decoder state, decoding flags, or exception settings.

Invalidate decoded state when middleware replaces the response body and define an explicit array conversion contract for array, empty, JSON null, malformed, scalar, and custom-decoder results.

Keep raw response access available, forward response methods and macros through resources, preserve caller JSON flags, and make resource construction and serialization fully typed.
Keep exact logical request data until supported middleware or callbacks replace the prepared PSR body, then invalidate it before later callbacks, recorders, fakes, and API middleware observe the request.

Gate stream-identity tracking behind configured Guzzle middleware and keep its internal option hidden from public callbacks and downstream handlers.

Correct media-type parsing, empty and malformed JSON handling, recursive query normalization, numeric query keys, single-key removal, static subtype returns, raw-body option ownership, and transient request initialization.
Use the structured request option only while it remains the authoritative caller payload.

Allow raw bodies and bodies replaced by supported request hooks to flow through Telescope\u0027s existing bounded PSR-body parser, while preserving the structured path for exact PHP payload values.

Add coverage for raw JSON body capture without duplicating HTTP client body-ownership logic in the watcher.
Move the time-limit test helper reset into tearDown so PHPUnit restores process-local state even when an assertion or callback throws.

Keep the test-owned cleanup at its owning boundary and avoid adding another framework-static cleanup registration.
Add max-level PHPStan fixtures for client and pending-request chains, all resource terminals, decorated value forwarding, resource narrowing, request mutation, and Arrayable contracts.

Pin the concrete resource annotation and runtime property pairing so both direct client calls and explicitly created pending requests retain their integration-specific result type.
Declare the Guzzle, PSR-7, promises, and PSR HTTP interfaces imported directly by API Client, HTTP, and Support.

Remove API Client\u0027s obsolete Engine dependency and keep root constraints authoritative for monorepo development.

Add bounded metadata tests that compare split constraints with the root manifest instead of copying version literals into test expectations.
Declare direct Guzzle, PSR HTTP, Flysystem, URI, and Monolog owners in the framework split packages that import them.

Advertise Faker and external search clients only where they enable concrete factory or testing features, and document the client packages required by the external search test traits.

Extend package metadata coverage without pinning change-prone version literals.
Declare each direct Guzzle, promises, or PSR HTTP dependency used by Inertia, Socialite, and Telescope.

Remove the unused API Client dependency from Reverb so its split manifest reflects the code it actually ships.

Cover every corrected relationship with focused metadata tests and keep package ordering consistent with repository conventions.
Document API Client as an integration-focused sister to the HTTP client using Laravel-style prose and examples.

Cover reusable client classes, optional typed configuration, pending requests, context, resources, errors, request and response middleware, structured and raw mutation, transport customization, fakes, and middleware lifetimes.

Keep the package README minimal and add the guide to the Boost documentation index.
Record the final API Client architecture, public API decisions, lifecycle ownership, shared HTTP and Telescope boundaries, dependency corrections, testing strategy, and performance constraints.

Mark every durable API Client and cross-package finding revalidated, close the package checklist entry, and add the signed-off ledger assessment.

Retain the anti-overengineering rules and rejected alternatives needed to prevent future maintenance from reintroducing worker-shared state, callback growth, duplicate caches, or compatibility machinery.
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e9d18cd-2dfe-4af6-8ef7-d46ef3e19059

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change revises the API Client lifecycle, middleware, request and response conversion, shared HTTP body handling, package dependencies, documentation, audit records, and regression coverage.

Changes

API Client overhaul

Layer / File(s) Summary
Design and audit contracts
docs/plans/*
The audit plans define the revised API Client lifecycle, contracts, dependency ownership, documentation scope, validation gates, and completion criteria.
Shared HTTP request state
src/http/src/Client/*, src/telescope/src/Watchers/ClientRequestWatcher.php
Shared HTTP classes normalize queries, validate decoding, track prepared bodies, and synchronize logical request data after body changes.
API Client runtime contracts
src/api-client/src/*
ApiClient creates fresh pending requests. PendingRequest separates API middleware from HTTP middleware and executes synchronous requests with request context, response conversion, resource construction, and cleanup. ApiRequest and ApiResponse add explicit factories, mutation rules, array conversion, and state invalidation.
Package integration and documentation
composer.json, src/*/composer.json, src/api-client/README.md, src/boost/docs/*
Composer manifests declare HTTP dependencies and optional integrations. The API Client documentation is added and indexed.
Regression and type validation
tests/ApiClient/*, tests/Http/*, tests/*/PackageMetadataTest.php, tests/Telescope/*, tests/Testing/*, types/ApiClient/*
Tests cover request lifecycles, middleware, body and response state, metadata consistency, payload masking, timeout isolation, and generic type inference.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ApiClient
  participant PendingRequest
  participant HttpPendingRequest
  participant ApiResponse
  participant ApiResource
  ApiClient->>PendingRequest: createPendingRequest()
  PendingRequest->>HttpPendingRequest: send API request
  HttpPendingRequest-->>PendingRequest: HTTP response
  PendingRequest->>ApiResponse: createFrom(response)
  ApiResponse-->>PendingRequest: converted response
  PendingRequest->>ApiResource: make(response, request)
  ApiResource-->>ApiClient: typed resource
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main API Client architecture and request lifecycle changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit/api-client-correctness-ergonomics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@binaryfire

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR rebuilds API Client around operation-local pending requests and middleware while tightening shared HTTP request-state handling, Telescope payload reconstruction, package metadata, typing, tests, and documentation.

  • Creates fresh mutable request state for each API operation while retaining boot-stable client defaults.
  • Synchronizes structured request data, encoded bodies, headers, retry context, and response reconstruction.
  • Updates HTTP query and payload normalization and Telescope’s structured-metadata fallback.
  • Aligns split-package dependencies and expands first-party documentation and regression coverage.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains in the eligible follow-up review scope.

No blocking failure remains.

Important Files Changed

Filename Overview
src/api-client/src/ApiClient.php Reworks the client into a boot-stable integration definition that creates and configures a fresh pending request for each operation.
src/api-client/src/PendingRequest.php Adds operation-local middleware, retry-aware request bridging, context propagation, synchronous transport boundaries, and unconditional transient-state cleanup.
src/api-client/src/ApiRequest.php Introduces coherent structured-data and raw-body mutation with media-type, framing, and stream synchronization.
src/api-client/src/ApiResponse.php Preserves HTTP response metadata while adding API resource conversion and decoded-state invalidation after body replacement.
src/http/src/Client/PendingRequest.php Refines query normalization, logical payload ownership, middleware body tracking, and request preparation across transport, fake, and recorder paths.
src/http/src/Client/Request.php Extends request metadata so logical structured data remains available only while authoritative.
src/telescope/src/Watchers/ClientRequestWatcher.php Uses authoritative structured metadata when available and otherwise parses the bounded PSR request body.
composer.json Declares directly imported Guzzle promises and PSR-7 packages at the repository root.
src/api-client/composer.json Updates split-package dependency ownership for the rebuilt API Client.
types/ApiClient/ApiClient.php Updates the static-analysis surface to match the refined fluent client and typed-resource API.

Reviews (3): Last reviewed commit: "docs(plans): record bodyless form behavi..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
tests/Broadcasting/PackageMetadataTest.php (1)

41-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard manifest keys before comparing constraints. All three loops index $composer['require'][$dependency] and $rootComposer['require'][$dependency] directly. If a split manifest omits a declared dependency, PHP raises an undefined-array-key error instead of a readable assertion failure. That hides the exact manifest defect these tests exist to detect.

  • tests/Broadcasting/PackageMetadataTest.php#L41-L41: add assertArrayHasKey($dependency, $composer['require']) inside the loop before the assertSame on line 42, so a missing guzzlehttp/guzzle declaration reports the package name.
  • tests/Contracts/PackageMetadataTest.php#L36-L36: add the same assertArrayHasKey guard inside the loop that now includes psr/http-message.
  • tests/Foundation/PackageMetadataTest.php#L47-L54: add the same assertArrayHasKey guard inside the new loop that compares guzzlehttp/guzzle, league/flysystem, league/uri, and monolog/monolog.
♻️ Proposed guard, shown for the Broadcasting loop
         foreach (['guzzlehttp/guzzle', 'psr/log', 'symfony/http-kernel'] as $dependency) {
+            $this->assertArrayHasKey($dependency, $composer['require']);
             $this->assertSame($rootComposer['require'][$dependency], $composer['require'][$dependency]);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Broadcasting/PackageMetadataTest.php` at line 41, Guard each dependency
lookup with assertArrayHasKey before comparing constraints: in
tests/Broadcasting/PackageMetadataTest.php lines 41-41,
tests/Contracts/PackageMetadataTest.php lines 36-36, and
tests/Foundation/PackageMetadataTest.php lines 47-54, add the guard inside the
respective dependency loops for $composer['require']; preserve the existing
assertions and dependency lists so missing declarations produce a readable
package-specific failure.
src/http/src/Client/PendingRequest.php (2)

740-740: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider normalizing the head() query too.

get() now routes the query through normalizeQuery(), but head() still forwards the raw value at Line 756. A Stringable or Arrayable query passed to head() therefore reaches Guzzle unnormalized. The declared parameter type of head() is narrower (array|string|null), so this is a consistency gap rather than a defect.

♻️ Optional alignment for `head()`
-    public function head(string $url, array|string|null $query = null): PromiseInterface|Response
+    public function head(string $url, Arrayable|array|JsonSerializable|string|null $query = null): PromiseInterface|Response
     {
         return $this->send(
             'HEAD',
             $url,
             func_num_args() === 1 ? [] : [
-                'query' => $query,
+                'query' => $this->normalizeQuery($query),
             ]
         );
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/http/src/Client/PendingRequest.php` at line 740, Update the head() method
to pass its query argument through normalizeQuery(), matching the get() path and
ensuring supported query values are normalized before reaching Guzzle.

1479-1509: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Body comparison uses object identity, so in-place stream writes stay undetected.

$request->getBody() !== $preparedBody compares StreamInterface instances. PSR-7 mutation through withBody() creates a new stream, so the common cases are covered. A middleware that writes into the existing stream (for example $request->getBody()->write(...)) keeps the same instance, so hypervel_data remains attached and then describes a body that no longer exists.

This is an accepted trade-off if in-place mutation is out of scope. If you want to close it, record the stream size and pointer alongside the instance, or document the limitation next to PREPARED_BODY_OPTION.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/http/src/Client/PendingRequest.php` around lines 1479 - 1509, Update
buildPreparedBodyHandler and buildBeforeSendingHandler to detect in-place stream
mutations by storing and comparing the prepared body’s relevant stream state,
such as instance, size, and pointer position, before deciding whether to unset
hypervel_data. Preserve the existing behavior for unchanged bodies and continue
removing PREPARED_BODY_OPTION before invoking the handler.
src/telescope/src/Watchers/ClientRequestWatcher.php (1)

154-158: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The documented behavior records a payload that can differ from the transmitted body.

The comment is accurate for this code path. recordRequest() captures $options before the handler stack runs, so the unset of hypervel_data performed by Hypervel\Http\Client\PendingRequest::buildBeforeSendingHandler() does not reach this snapshot. As a result, Telescope shows the caller's original structured payload even when a before-sending callback replaced the body.

Consider stating that consequence in the comment, so operators reading a Telescope entry know the payload reflects the caller's intent and not always the bytes on the wire.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/telescope/src/Watchers/ClientRequestWatcher.php` around lines 154 - 158,
Update the documentation comment near recordRequest() to explicitly state that
hypervel_data may reflect the caller’s original structured payload and can
differ from the transmitted request body when before-sending callbacks replace
it; retain the existing distinction for raw and third-party traffic parsed from
the PSR-7 body.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api-client/src/ApiResponse.php`:
- Around line 173-176: Update ApiResponse::offsetGet() to explicitly handle
offsets that are absent from the array, choosing the intended contract: return
null without emitting a PHP warning or throw a typed exception consistent with
the API. Preserve the existing toArray() error behavior and ensure
ApiResource::offsetGet() and ApiResource::__get() receive the defined result.

In `@src/api-client/src/PendingRequest.php`:
- Around line 303-376: The sendRequest/prepareClient flow must stop relying on
shared $this->activeRequest state, which is unsafe for concurrent reuse and can
be cleared before a resumed operation reads it. Pass the ApiRequest produced by
the beforeSending bridge through the request lifecycle or another
operation-local mechanism, then use that value to build the response resource
and remove the shared-state cleanup dependency; alternatively enforce and guard
a single-operation-only contract if the lifecycle cannot be made
operation-local.

In `@src/boost/docs/api-client.md`:
- Around line 252-258: Update the `query` method example to call `query` on the
already defined `$github` variable instead of `$client`, preserving the existing
request path and structured data.

In `@tests/ApiClient/ApiResponseTest.php`:
- Around line 129-137: Restore the original
HttpResponse::$defaultJsonDecodingFlags value around
testConfiguredThrowingJsonFlagsRemainObservable, using setUp/tearDown or a
try/finally that executes despite the expected JsonException. Ensure subsequent
ApiResponseTest cases, including testCreateFromPreservesHttpResponseState, see
the prior flag configuration.

---

Nitpick comments:
In `@src/http/src/Client/PendingRequest.php`:
- Line 740: Update the head() method to pass its query argument through
normalizeQuery(), matching the get() path and ensuring supported query values
are normalized before reaching Guzzle.
- Around line 1479-1509: Update buildPreparedBodyHandler and
buildBeforeSendingHandler to detect in-place stream mutations by storing and
comparing the prepared body’s relevant stream state, such as instance, size, and
pointer position, before deciding whether to unset hypervel_data. Preserve the
existing behavior for unchanged bodies and continue removing
PREPARED_BODY_OPTION before invoking the handler.

In `@src/telescope/src/Watchers/ClientRequestWatcher.php`:
- Around line 154-158: Update the documentation comment near recordRequest() to
explicitly state that hypervel_data may reflect the caller’s original structured
payload and can differ from the transmitted request body when before-sending
callbacks replace it; retain the existing distinction for raw and third-party
traffic parsed from the PSR-7 body.

In `@tests/Broadcasting/PackageMetadataTest.php`:
- Line 41: Guard each dependency lookup with assertArrayHasKey before comparing
constraints: in tests/Broadcasting/PackageMetadataTest.php lines 41-41,
tests/Contracts/PackageMetadataTest.php lines 36-36, and
tests/Foundation/PackageMetadataTest.php lines 47-54, add the guard inside the
respective dependency loops for $composer['require']; preserve the existing
assertions and dependency lists so missing declarations produce a readable
package-specific failure.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fe0f9a3-7534-4a51-ab4a-20dcb82a544b

📥 Commits

Reviewing files that changed from the base of the PR and between 294791e and cc9c065.

📒 Files selected for processing (49)
  • composer.json
  • docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
  • docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
  • docs/plans/2026-08-09-2105-api-client-correctness-laravel-ergonomics-and-lifecycles.md
  • src/api-client/README.md
  • src/api-client/composer.json
  • src/api-client/src/ApiClient.php
  • src/api-client/src/ApiRequest.php
  • src/api-client/src/ApiResource.php
  • src/api-client/src/ApiResponse.php
  • src/api-client/src/Concerns/HasContext.php
  • src/api-client/src/Exceptions/InvalidResourceDataException.php
  • src/api-client/src/PendingRequest.php
  • src/boost/docs/api-client.md
  • src/boost/docs/documentation.md
  • src/boost/docs/testing.md
  • src/broadcasting/composer.json
  • src/contracts/composer.json
  • src/database/composer.json
  • src/foundation/composer.json
  • src/http/composer.json
  • src/http/src/Client/PendingRequest.php
  • src/http/src/Client/Request.php
  • src/inertia/composer.json
  • src/reverb/composer.json
  • src/socialite/composer.json
  • src/support/composer.json
  • src/telescope/composer.json
  • src/telescope/src/Watchers/ClientRequestWatcher.php
  • tests/ApiClient/ApiClientTest.php
  • tests/ApiClient/ApiRequestTest.php
  • tests/ApiClient/ApiResourceTest.php
  • tests/ApiClient/ApiResponseTest.php
  • tests/ApiClient/PackageMetadataTest.php
  • tests/ApiClient/PendingRequestTest.php
  • tests/Broadcasting/PackageMetadataTest.php
  • tests/Contracts/PackageMetadataTest.php
  • tests/Database/PackageMetadataTest.php
  • tests/Foundation/PackageMetadataTest.php
  • tests/Http/HttpClientTest.php
  • tests/Http/PackageMetadataTest.php
  • tests/Inertia/PackageMetadataTest.php
  • tests/Reverb/PackageMetadataTest.php
  • tests/Socialite/PackageMetadataTest.php
  • tests/Support/PackageMetadataTest.php
  • tests/Telescope/PackageMetadataTest.php
  • tests/Telescope/Watchers/ClientRequestWatcherTest.php
  • tests/Testing/PHPUnit/TimeLimitTest.php
  • types/ApiClient/ApiClient.php
💤 Files with no reviewable changes (1)
  • src/reverb/composer.json

Comment thread src/api-client/src/ApiResponse.php
Comment thread src/api-client/src/PendingRequest.php
Comment thread src/boost/docs/api-client.md
Comment thread tests/ApiClient/ApiResponseTest.php
Normalize nested Stringable, JsonSerializable, and Arrayable values consistently for query, form, and multipart requests while keeping final wire options and logical request data under their correct owners.

Preserve multipart-configured GET and HEAD query payloads, align HEAD with GET across HTTP and API Client types, and recover URL-embedded HEAD query data for assertions, fakes, and recorders.

Add focused runtime and static-analysis coverage for valid nested values, rejected result shapes, logical and wire agreement, and the public exception contracts.
Keep array conversion strict for invalid response bodies while making property and array access return null when a successfully decoded resource does not contain the requested key.

Cover direct response offsets and resource property access so absent fields follow familiar Laravel data-access behavior without hiding malformed or scalar response failures.
Require root and split dependency keys to exist before comparing their constraints so a missing entry cannot silently satisfy a null-to-null assertion.

Apply the same direct presence contract across every affected package metadata test while retaining existing broader split checks and avoiding copied version literals or helper machinery.
Fix the query and raw-response examples, define the pending request concurrency boundary, and document null reads for fields missing from valid resource arrays.

Clarify that Telescope's structured request payload comes from the original Guzzle options, so later before-sending body replacements can change transport bytes without rewriting the recorded caller intent.
Update the final design for recursive structured request normalization, split logical and wire ownership, multipart query preservation, GET and HEAD URL-query observation, and guarded metadata comparisons.

Keep the completion and performance criteria aligned with the implemented code and the focused regressions that protect each corrected boundary.
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@coderabbitai Review follow-up is complete.

Accepted and fixed:

  • Missing API response fields now return null after valid array conversion, with invalid body behavior unchanged.
  • The API Client examples now use variables defined in each snippet.
  • Query, form, and multipart values normalize nested Stringable, JsonSerializable, and Arrayable inputs at both the wire and logical request-data owners.
  • Invalid normalized shapes now fail with format-specific framework exceptions.
  • Multipart-configured GET and HEAD requests retain string and URL-embedded query data, and HEAD now observes URL queries consistently with GET.
  • HTTP and API Client HEAD signatures, facade metadata, terminal exception contracts, and static type fixtures are aligned.
  • Package metadata comparisons now prove both dependency keys exist before comparing constraints.
  • Telescope documentation now explains why structured caller input can differ from bytes replaced later in the handler stack.

Two suggestions did not result in runtime code:

  • A pending request remains a mutable, single-operation Laravel-style builder. The public guide now states that contract; adding a lock, context slot, or parallel lifecycle channel would be unnecessary machinery for unsupported concurrent reuse.
  • HTTP response static state remains owned by the global after-test subscriber. Adding a second reset inside one API Client test would duplicate the authoritative cleanup path.

The full repository formatter, static analysis, parallel test suite, Testbench suite, and dogfood package checks are green. The follow-up also received a complete final review after the last GET/HEAD correction.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@binaryfire Thanks for the detailed follow-up.

The accepted findings are addressed.

The stated PendingRequest lifecycle contract makes concurrent reuse unsupported by design. The additional locking or lifecycle state is not required.

The global after-test subscriber remains the authoritative owner for HTTP response static-state cleanup. A duplicate reset in an API Client test is not required.

🐇 ✅


✏️ Learnings added
Learnt from: binaryfire
URL: https://github.com/hypervel/components-backup/pull/11

Timestamp: 2026-08-10T12:24:15.543Z
Learning: In `src/api-client`, `PendingRequest` is intentionally a mutable, single-operation Laravel-style builder. Concurrent reuse of one pending-request instance is unsupported, and reviews should not require locks, context slots, or parallel lifecycle channels for that use case.

Learnt from: binaryfire
URL: https://github.com/hypervel/components-backup/pull/11

Timestamp: 2026-08-10T12:24:15.543Z
Learning: HTTP response static state is reset by the global after-test subscriber. API Client tests must rely on this authoritative cleanup path and should not add redundant local static-state resets.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@binaryfire

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/http/src/Client/PendingRequest.php`:
- Around line 1212-1217: Update parseHttpOptions() handling of the form_params
key to allow null values for bodyless asForm() requests: skip the option or
normalize null to an empty array before the array validation. Preserve
validation and normalization for non-null form payloads so asForm()->get(),
asForm()->head(), and empty asForm()->delete() proceed without throwing.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 635f22a0-353d-4149-b1e0-ffeacd939b61

📥 Commits

Reviewing files that changed from the base of the PR and between cc9c065 and 4bb59e3.

📒 Files selected for processing (32)
  • docs/plans/2026-08-09-2105-api-client-correctness-laravel-ergonomics-and-lifecycles.md
  • src/api-client/src/ApiResponse.php
  • src/api-client/src/PendingRequest.php
  • src/boost/docs/api-client.md
  • src/http/src/Client/PendingRequest.php
  • src/support/src/Facades/Http.php
  • src/telescope/src/Watchers/ClientRequestWatcher.php
  • tests/ApiClient/ApiResourceTest.php
  • tests/ApiClient/ApiResponseTest.php
  • tests/ApiClient/PackageMetadataTest.php
  • tests/ApiClient/PendingRequestTest.php
  • tests/Broadcasting/PackageMetadataTest.php
  • tests/Concurrency/PackageMetadataTest.php
  • tests/Contracts/PackageMetadataTest.php
  • tests/Di/PackageMetadataTest.php
  • tests/Foundation/PackageMetadataTest.php
  • tests/Http/HttpClientTest.php
  • tests/Http/PackageMetadataTest.php
  • tests/Inertia/PackageMetadataTest.php
  • tests/Mail/PackageMetadataTest.php
  • tests/Notifications/PackageMetadataTest.php
  • tests/Passkeys/PackageMetadataTest.php
  • tests/Permission/PackageMetadataTest.php
  • tests/Process/PackageMetadataTest.php
  • tests/Routing/PackageMetadataTest.php
  • tests/Scout/PackageMetadataTest.php
  • tests/Socialite/PackageMetadataTest.php
  • tests/Support/PackageMetadataTest.php
  • tests/Telescope/PackageMetadataTest.php
  • tests/Testing/PackageMetadataTest.php
  • tests/View/PackageMetadataTest.php
  • types/ApiClient/ApiClient.php
🚧 Files skipped from review as they are similar to previous changes (13)
  • tests/Contracts/PackageMetadataTest.php
  • tests/Broadcasting/PackageMetadataTest.php
  • tests/Socialite/PackageMetadataTest.php
  • src/telescope/src/Watchers/ClientRequestWatcher.php
  • tests/Inertia/PackageMetadataTest.php
  • tests/Support/PackageMetadataTest.php
  • src/api-client/src/ApiResponse.php
  • tests/Http/PackageMetadataTest.php
  • tests/Foundation/PackageMetadataTest.php
  • tests/Telescope/PackageMetadataTest.php
  • src/api-client/src/PendingRequest.php
  • docs/plans/2026-08-09-2105-api-client-correctness-laravel-ergonomics-and-lifecycles.md
  • tests/ApiClient/ApiResponseTest.php

Comment thread src/http/src/Client/PendingRequest.php
Treat a directly null form_params option as the framework-planted absence of a payload while continuing to reject caller-supplied structured values that normalize to null or another non-array shape.

Restore bodyless GET, queried GET, HEAD, and empty DELETE behavior for the HTTP client and API Client integrations configured with asForm, with focused regressions at both public surfaces.
Document the distinction between a directly null form option planted for a bodyless request and caller-supplied structured form data that resolves to null.

Extend the verification matrix to cover bodyless form-configured HTTP and API Client read and delete operations.
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@coderabbitai The additional form-request finding is resolved.

The root cause was the new strict form validator treating a null body-format option planted by the framework as caller-supplied data. The fix preserves that direct null unchanged, which restores the previous Guzzle behavior without manufacturing a request body. Structured values that resolve to null or another non-array shape remain invalid.

Coverage now includes bodyless GET, GET with query data, HEAD, and empty DELETE at the HTTP client boundary and through an API Client whose integration configuration applies asForm. The null-versus-normalized-null distinction is also pinned so a later cleanup cannot move the guard below structured normalization.

The correction passed focused formatting, static analysis, and HTTP/API Client tests, followed by final review sign-off.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@binaryfire Confirmed. The finding is addressed.

The direct framework-provided null body-format option now preserves the prior bodyless Guzzle behavior. Structured values that normalize to null or another non-array shape remain invalid. The coverage protects the ordering requirement at both the HTTP client and API Client boundaries.

🐇 ✅


✏️ Learnings added
Learnt from: binaryfire
URL: https://github.com/hypervel/components-backup/pull/11

Timestamp: 2026-08-10T12:50:25.453Z
Learning: In `src/http` form request handling, a framework-provided direct `null` body-format option must remain unchanged so bodyless requests preserve Guzzle behavior. The guard must run before structured-value normalization. Values that originate from structured input and normalize to `null` or any non-array shape must remain invalid and raise the format-specific exception. This behavior applies at the HTTP client boundary and through `src/api-client` integrations that configure `asForm`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@binaryfire binaryfire closed this Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant