Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4e4207d
Import Laravel AI SDK documentation
binaryfire Oct 7, 2026
3848ffe
Document AI SDK port plan and follow-up work
binaryfire Oct 7, 2026
680c97c
Define flexible AI port review checkpoints
binaryfire Oct 7, 2026
7fc20b3
Separate logical database connections from pooled PDO sessions
binaryfire Oct 7, 2026
f72287c
Isolate transaction records from copied coroutine context
binaryfire Oct 7, 2026
cbf177c
Avoid duplicate coroutine context lookups in framework services
binaryfire Oct 7, 2026
38c1054
Stream HTTP responses incrementally and cancel disconnected producers
binaryfire Oct 7, 2026
4d0f4c4
Record the finalized HTTP transport ownership and cancellation design
binaryfire Oct 7, 2026
4fa130f
Merge pull request #652 from hypervel/upstream-sync-jwt-reconciliation
binaryfire Oct 7, 2026
b365971
Expose file and directory existence checks on the filesystem contract
binaryfire Oct 7, 2026
d4933ef
Expose named property schemas from object types
binaryfire Oct 7, 2026
6a172b6
Cache recursive trait membership in class metadata
binaryfire Oct 7, 2026
c55f784
Observe directory arrivals for fswatch roots and skill paths
binaryfire Oct 7, 2026
3c6fb52
Preserve dotenv ownership when restoring Testbench environments
binaryfire Oct 7, 2026
65abe51
Remove the unreachable streaming connection-cache option guard
binaryfire Oct 7, 2026
0085a64
Release idle database sessions before outgoing HTTP requests
binaryfire Oct 7, 2026
7c0550d
Bound streaming receive buffering and order completion callbacks
binaryfire Oct 7, 2026
5ed7961
Add repeatable database contention and HTTP transport benchmarks
binaryfire Oct 7, 2026
57898ed
Retain more idle streaming connections for concurrent requests
binaryfire Oct 7, 2026
88af240
Record transport dependency follow-ups and buffered reuse regression
binaryfire Oct 7, 2026
b5625ad
Keep the framework changes separate from the AI SDK port
binaryfire Oct 7, 2026
0dda010
Merge remote-tracking branch 'origin/0.4' into fix/framework-io-lifec…
binaryfire Oct 7, 2026
ffe1128
Preserve database driver identity and listener reconnects
binaryfire Oct 7, 2026
6d1f614
Release discarded streaming handles without cyclic collection
binaryfire Oct 7, 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
10 changes: 10 additions & 0 deletions docs/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,22 @@

- Investigate FTP support with Swoole's built-in coroutine FTP implementation. It supplies `ftp_*` functions but is not discoverable as `ext-ftp`, so Composer rejects `league/flysystem-ftp` and `RequiresPhpExtension('ftp')` skips the driver test. Resolve normal development and production installation without bypassing dependency checks, then add the adapter to root `require-dev`, use a test requirement that accepts either FTP implementation, and update the installation guidance. Basic transfers through Hypervel and Flysystem have been verified inside a Swoole coroutine.

## AI and MCP

- Port the MCP package in its separately assigned session, retaining the AI SDK's optional MCP adapters in the AI port. Audit client/session lifetimes and safe bounded reuse, HTTP/stdio cancellation and cleanup, coroutine-local request resolution through server/tool/resource paths, and lazy response iteration. Resolve the current MCP Request from CoroutineContext keyed by Request::class, shared with the AI adapter's MCP_REQUEST constant. Run the AI adapter's real MCP-dependent tests once the package is available and remove analysis annotations made obsolete by that dependency. Finalize the AI docs' MCP section and links when the MCP package and its documentation page land. AI's ordinary tools and providers must remain usable without MCP. Do not ship placeholder MCP classes or expand the AI port into the full MCP package.

## HTTP Client

- Recheck streaming performance once a build containing the Swoole native `curl_multi_select()` fix is available. The affected hook can consume ready response data internally and then wait for another event or its timeout before returning. Hypervel therefore sometimes delays an SSE event or completion by up to one second, even though the data has arrived. This requires no Hypervel workaround.
- Run `Hypervel\Tests\Http\Client\CurlStreamingHandlerTest::testNativeWaitReportsDataArrivingBetweenPerformAndSelect` against the patched extension with its temporary `SWOOLE_VERSION_ID <= 60203` skip removed locally. Verify both mid-stream readiness and completion, plus the existing quiet-provider timeout/cancellation and cleanup tests. Update the committed version gate only after identifying the actual fixed release; the current gate does not establish that a later release is fixed.
- In an idle window, use `tests/Benchmarks/HttpTransport` and its README to repeat alternating paired measurements before/after high-water buffering on the same patched runtime. Keep dependencies, TLS verification, idle-retention bound, PHP/OPcache/JIT/GC settings and request concurrency identical. Cover paced events, unpaced bulk responses and slow consumers, then repeat sustained concurrent traffic. Check first-event and completion latency (including outliers), throughput, client CPU, memory, connection reuse and descriptor cleanup. Confirm the one-second stalls disappear without changing Hypervel's select interval or buffering strategy.
- Record the native PR/build, settings and raw reports before publishing buffering performance claims. If a verified problem remains, investigate its owning layer rather than adding polling, shorter waits or a producer coroutine to hide it.
- Resolve the Guzzle-dependent test skips when the upstream changes are available: adopt configurable idle-handle retention for named buffered requests and unskip `HttpConnectionTest::testNamedBufferedRequestsReuseConnectionsAcrossConcurrentBursts`; once shared StreamHandler request deadlines are fixed, remove the `ClientInterface::MAJOR_VERSION >= 8` skip from `HttpClientStreamingTest::testConcurrentFallbackRequestsKeepIndependentDeadlines`. Verify both supported Guzzle branches when adopting these changes.
- Once the HTTP client can optionally use Swoole's coroutine HTTP client as its transport instead of curl, explore and benchmark that transport for Inertia SSR requests. Each SSR render posts the page JSON to a local SSR server through the `inertia-ssr` connection (`HttpGateway::CONNECTION`), which makes it a good candidate for the alternative transport. Compare the curl and Swoole transports on that connection against a local SSR server under concurrent load, measuring latency, throughput, client CPU, memory and connection reuse, and use the Swoole transport for the SSR connection if it is a clear improvement.

## HTTP Server

- Integrate a server-level HTTP/2 stream-cancel event once Swoole exposes it in a supported release, and raise the `ext-swoole` constraint to that release when integrating it. The current `RST_STREAM` path removes the stream without notifying PHP, so an AI response producer waiting on a silent provider cannot stop immediately when its caller cancels that stream. Use the event's connection and stream identities with Hypervel's active-response map to cancel only the affected opted-in producer. Preserve sibling streams, application close callbacks and terminable work. Whole-connection closes continue using existing onClose handling; no native per-response subscription is needed. Keep the AI package usable on existing supported Swoole releases with connection-close handling where available, failed-write cleanup and provider timeouts; document the remaining HTTP/2 limitation. This enhancement is not an AI-port release prerequisite. Use the actual released event name and arguments rather than polling or anticipating an unreleased API.
- Require a Swoole release that resets signal-listener state in forked server workers before releasing Hypervel 0.4. In Swoole 6.2.3 and earlier, a worker forked after the manager calls `Process::signal()` inherits the listener count, so `Coroutine\System::waitSignal()` fails in it. Hypervel's SIGINT shutdown handling registers a manager callback in both server modes, so after a reload, `max_request` recycling or a crash restart, replacement workers stop receiving configured signal handlers and Artisan traps. Once a fixed release is verified, raise the `ext-swoole` constraint and remove the version skip from `ShutdownOnInterruptListenerTest::testReplacementWorkersKeepTheirSignalHandlers()`.
- Remove trailer-stream one-chunk lookahead once the minimum supported Swoole release includes [swoole-src#6124](https://github.com/swoole/swoole-src/pull/6124). Current releases send an empty `END_STREAM` DATA frame before trailer HEADERS when `end()` receives no body after `write()`, so `ResponseBridge` retains the final chunk for `end($chunk)` and delays delivery by one chunk. Once fixed, raise the `ext-swoole` constraint, write every chunk immediately, emit trailers, call bare `end()`, invert the deterministic bridge ordering tests, and add real gRPC incremental-delivery coverage.

Expand Down
7 changes: 2 additions & 5 deletions src/auth/src/AuthManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,8 @@ protected function getConfig(string $name): ?array
*/
public function getDefaultDriver(): string
{
if (CoroutineContext::has(self::DEFAULT_GUARD_CONTEXT_KEY)) {
return CoroutineContext::get(self::DEFAULT_GUARD_CONTEXT_KEY);
}

return $this->app->make('config')->string('auth.defaults.guard');
return CoroutineContext::get(self::DEFAULT_GUARD_CONTEXT_KEY)
?? $this->app->make('config')->string('auth.defaults.guard');
}

/**
Expand Down
6 changes: 4 additions & 2 deletions src/auth/src/Passwords/PasswordBrokerManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@ public function resolveBrokerNameForGuard(UnitEnum|string $guard): ?string
*/
public function getDefaultDriver(): string
{
if (CoroutineContext::has(self::DEFAULT_BROKER_CONTEXT_KEY)) {
return CoroutineContext::get(self::DEFAULT_BROKER_CONTEXT_KEY);
$name = CoroutineContext::get(self::DEFAULT_BROKER_CONTEXT_KEY);

if ($name !== null) {
return $name;
}

$guard = $this->app->make(AuthFactoryContract::class)->getDefaultDriver();
Expand Down
14 changes: 9 additions & 5 deletions src/container/src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -732,13 +732,15 @@ public function extend(string $abstract, Closure $closure): void
$this->autoSingletons[$abstract] = $closure($this->autoSingletons[$abstract], $this);

$this->rebound($abstract);
} elseif ($this->isScoped($abstract) && CoroutineContext::has(self::SCOPED_CONTEXT_PREFIX . $abstract)) {
} elseif ($this->isScoped($abstract)
&& ($instance = CoroutineContext::get(self::SCOPED_CONTEXT_PREFIX . $abstract)) !== null
) {
// Apply the extender to the current cached scoped instance immediately.
// Also queue it in $this->extenders for future resolutions — unlike singletons
// (which persist for the worker lifetime), scoped instances are destroyed each
// request by forgetScopedInstances() and must re-apply extenders on rebuild.
$contextKey = self::SCOPED_CONTEXT_PREFIX . $abstract;
CoroutineContext::set($contextKey, $closure(CoroutineContext::get($contextKey), $this));
CoroutineContext::set($contextKey, $closure($instance, $this));

$this->extenders[$abstract][] = $closure;

Expand Down Expand Up @@ -1191,8 +1193,10 @@ protected function resolve(
&& ! $needsContextualBuild
) {
$contextKey = self::SCOPED_CONTEXT_PREFIX . $abstract;
if (CoroutineContext::has($contextKey)) {
return CoroutineContext::get($contextKey);
$instance = CoroutineContext::get($contextKey);

if ($instance !== null) {
return $instance;
}
}

Expand Down Expand Up @@ -1338,7 +1342,7 @@ protected function resolve(
if ($publishedCache === 'scoped') {
$contextKey = self::SCOPED_CONTEXT_PREFIX . $abstract;

if (CoroutineContext::has($contextKey)
if ($publishedValue !== null
&& CoroutineContext::get($contextKey) === $publishedValue
) {
CoroutineContext::forget($contextKey);
Expand Down
15 changes: 15 additions & 0 deletions src/context/src/CoroutineContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,21 @@ private static function prepareForCopy(array $values): array
return $values;
}

/**
* Store a value in non-coroutine context only.
*
* Tests only. This writes worker-global storage even from a coroutine,
* so request-time use can leak state between concurrent requests.
*
* @param TKey $id
* @param TValue $value
* @return TValue
*/
public static function setNonCoroutine(UnitEnum|string $id, mixed $value): mixed
{
return static::$nonCoroutineContext[is_string($id) ? $id : enum_value($id)] = $value;
}

/**
* Get a value from non-coroutine context only.
*
Expand Down
12 changes: 11 additions & 1 deletion src/contracts/src/Filesystem/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,20 @@ interface Filesystem
public function path(string $path): string;

/**
* Determine if a file exists.
* Determine if a file or directory exists.
*/
public function exists(string $path): bool;

/**
* Determine if a file exists.
*/
public function fileExists(string $path): bool;

/**
* Determine if a directory exists.
*/
public function directoryExists(string $path): bool;

/**
* Get the contents of a file.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/contracts/src/JsonSchema/JsonSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface JsonSchema
/**
* Create a new object schema instance.
*
* @param array<string, Type>|(Closure(JsonSchema): array<string, Type>) $properties
* @param array<int|string, Type>|(Closure(JsonSchema): array<int|string, Type>) $properties
*/
public function object(Closure|array $properties = []): ObjectType;

Expand Down
3 changes: 3 additions & 0 deletions src/contracts/src/Routing/ResponseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use BackedEnum;
use Closure;
use Hypervel\Http\IterableStreamedResponse;
use Hypervel\Http\JsonResponse;
use Hypervel\Http\RedirectResponse;
use Hypervel\Http\Response;
Expand Down Expand Up @@ -44,6 +45,8 @@ public function jsonp(string $callback, mixed $data = [], int $status = 200, arr

/**
* Create a new event stream response.
*
* @return IterableStreamedResponse
*/
public function eventStream(Closure $callback, array $headers = [], StreamedEvent|string|null $endStreamWith = '</stream>'): StreamedResponse;

Expand Down
1 change: 1 addition & 0 deletions src/database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Documentation: https://hypervel.org/docs/database

## Differences From Laravel

- Outgoing framework HTTP requests return idle database sessions to their pools. Code using temporary tables, session locks or retained raw PDOs across these calls must use `withPinnedSession()`; ordinary queries and active transactions keep their behavior. See [releasing and pinning connections](https://hypervel.org/docs/database#releasing-and-pinning-connections).
- MySQL and MariaDB connections require `strict` or `modes` in their configuration instead of implicitly inheriting the server's SQL mode, so SQL literals can be escaped correctly.
- Laravel's external database pooler support uses a `::direct` connection suffix. Hypervel instead uses normal named connections for each endpoint and `migrations_connection` for schema and migration paths. This keeps direct and pooled endpoints as normal configured connections with their own pool settings, so Hypervel does not support Laravel's `::direct` suffix.
- Laravel exposes PDO methods on its base connection class. Hypervel's base `Connection` is driver-neutral, while PDO-backed drivers extend `PdoConnection`. Code that requires direct PDO access should accept or narrow to `PdoConnection`. When bringing in Laravel database updates, keep driver-neutral connection behavior on `Connection`, PDO mechanics on `PdoConnection`, and driver-specific behavior on the matching connection, grammar, schema builder, or processor.
Expand Down
5 changes: 3 additions & 2 deletions src/database/src/Concerns/ManagesTransactions.php
Original file line number Diff line number Diff line change
Expand Up @@ -408,9 +408,10 @@ protected function handleRollBackException(Throwable $e): void
protected function forgetLostConnection(): void
{
try {
$this->resetTransactionState();
} finally {
$this->forgetDriverResources();
} finally {
// Rollback callbacks may reconnect; only forget the failed session.
$this->resetTransactionState();
}
}

Expand Down
Loading
Loading