Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
3e7afc2
Add getMiddlewareGroups() to HTTP Kernel contract
binaryfire Apr 13, 2026
1c12b70
Fix BladeCompiler::directive() to accept callable
binaryfire Apr 13, 2026
89fac00
Fix HTTP client Response JSON decode caching
binaryfire Apr 13, 2026
71d0732
Add tests for HTTP client JSON decode caching
binaryfire Apr 13, 2026
0d21cfd
Fix AssertableJsonString::jsonSearchStrings() to accept int keys
binaryfire Apr 13, 2026
0a623d4
Add baseUrl property to Testbench TestCase
binaryfire Apr 13, 2026
0980130
Add Inertia package skeleton
binaryfire Apr 13, 2026
2284fb4
Register inertia package in root composer.json
binaryfire Apr 13, 2026
1dbea59
Add InertiaState per-request Context state bag
binaryfire Apr 13, 2026
cfd23ca
Add Inertia support constants (Header, SessionKey)
binaryfire Apr 13, 2026
c7f80ae
Add Inertia prop type interfaces
binaryfire Apr 13, 2026
8881b19
Add Inertia provider interfaces
binaryfire Apr 13, 2026
a6043c2
Add Inertia DTOs and exceptions
binaryfire Apr 13, 2026
43d48e3
Add Inertia ResolvesCallables trait
binaryfire Apr 13, 2026
2652b6f
Add Inertia DefersProps trait
binaryfire Apr 13, 2026
005c2b6
Add Inertia MergesProps trait
binaryfire Apr 13, 2026
565768c
Add Inertia ResolvesOnce trait
binaryfire Apr 13, 2026
f6009d4
Add Inertia SSR interfaces
binaryfire Apr 13, 2026
fa4d1cc
Add Inertia SSR value types (Response, SsrErrorType)
binaryfire Apr 13, 2026
823965d
Add Inertia SSR failure event
binaryfire Apr 13, 2026
120779e
Add Inertia SSR exception
binaryfire Apr 13, 2026
f5b8dad
Add Inertia SSR bundle detector with worker-lifetime caching
binaryfire Apr 13, 2026
e893b44
Add Inertia SSR HTTP gateway
binaryfire Apr 13, 2026
252fd85
Port AlwaysProp from upstream Inertia adapter
binaryfire Apr 13, 2026
dffcc46
Port DeferProp from upstream Inertia adapter
binaryfire Apr 13, 2026
75a4579
Port MergeProp from upstream Inertia adapter
binaryfire Apr 13, 2026
2352a9d
Port OnceProp from upstream Inertia adapter
binaryfire Apr 13, 2026
e3f90b0
Port OptionalProp from upstream Inertia adapter
binaryfire Apr 13, 2026
5762a63
Implement ScrollMetadata for paginator integration
binaryfire Apr 13, 2026
9f3f282
Implement ScrollProp for infinite scroll support
binaryfire Apr 13, 2026
63e4c2f
Props resolution engine for Inertia responses
binaryfire Apr 13, 2026
16eef63
Stateless ResponseFactory with Context-backed per-request state
binaryfire Apr 13, 2026
a874781
Inertia Response with SSR state via CoroutineContext
binaryfire Apr 13, 2026
13c4ce6
Inertia route controller
binaryfire Apr 13, 2026
2605486
Inertia middleware with worker-lifetime version caching
binaryfire Apr 13, 2026
8f08481
History encryption middleware for Inertia
binaryfire Apr 13, 2026
e9b414a
Inertia middleware subclasses (EncryptHistory, EnsureGetOnRedirect)
binaryfire Apr 13, 2026
056434e
Exception response rendering for Inertia error pages
binaryfire Apr 13, 2026
0adef48
Blade directives (@inertia, @inertiaHead) with Context-based SSR disp…
binaryfire Apr 13, 2026
9576679
Blade view components for Inertia SSR rendering
binaryfire Apr 13, 2026
0d2891b
AssertableInertia test assertion class
binaryfire Apr 13, 2026
7657ce8
ReloadRequest test helper for partial reload assertions
binaryfire Apr 13, 2026
53d1dbd
TestResponse macros for Inertia assertions
binaryfire Apr 13, 2026
6bd8605
Inertia facade
binaryfire Apr 13, 2026
8a1c7d1
InertiaServiceProvider with auto-singleton ResponseFactory/HttpGateway
binaryfire Apr 13, 2026
88a6a06
Inertia helper functions (inertia, inertia_location)
binaryfire Apr 13, 2026
44386be
Inertia config with SSR timeouts, backoff, and error handling
binaryfire Apr 13, 2026
b7c8c64
Middleware generator stub for Inertia
binaryfire Apr 13, 2026
f984be8
Register Inertia flushState calls in AfterEachTestSubscriber
binaryfire Apr 13, 2026
ce1b9c6
Inertia test fixtures (views, stubs, enums, middleware)
binaryfire Apr 13, 2026
6f526f0
Inertia test base class and InteractsWithUserModels trait
binaryfire Apr 13, 2026
3a6c226
Port AlwaysPropTest
binaryfire Apr 13, 2026
84fc7d1
Port ComponentTest with Context-based SSR state
binaryfire Apr 13, 2026
cdf2294
Port ControllerTest
binaryfire Apr 13, 2026
4190238
Port DeepMergePropTest
binaryfire Apr 13, 2026
3a3a919
Port DeferPropTest
binaryfire Apr 13, 2026
758b7b7
Port DirectiveTest
binaryfire Apr 13, 2026
b0128d3
Port ExceptionResponseTest
binaryfire Apr 13, 2026
94597a6
Port HelperTest
binaryfire Apr 13, 2026
915ac26
Port HistoryTest
binaryfire Apr 13, 2026
17d23f4
Port HttpGatewayTest with circuit breaker and scalar JSON regression …
binaryfire Apr 13, 2026
eca1f2c
Port MergePropTest
binaryfire Apr 13, 2026
5915592
Port MiddlewareTest with version caching tests
binaryfire Apr 13, 2026
08f8411
Port OncePropTest
binaryfire Apr 13, 2026
b8af4dd
Port OptionalPropTest
binaryfire Apr 13, 2026
8689881
Port PropsResolverTest
binaryfire Apr 13, 2026
4d81b0a
Port ResponseFactoryTest
binaryfire Apr 13, 2026
c80a560
Port ResponseTest
binaryfire Apr 13, 2026
e654174
Port ScrollMetadataTest with RequestContext-based pagination
binaryfire Apr 13, 2026
fe43bef
Port ScrollPropTest with RequestContext-based header simulation
binaryfire Apr 13, 2026
f1f16a4
Port ServiceProviderTest as InertiaServiceProviderTest
binaryfire Apr 13, 2026
88817c4
Port SsrRenderFailedTest
binaryfire Apr 13, 2026
68d0215
Port CheckSsrTest
binaryfire Apr 13, 2026
0431e3c
Port StartSsrTest
binaryfire Apr 13, 2026
d3a302d
Port AssertableInertiaTest
binaryfire Apr 13, 2026
1608138
Port TestResponseMacrosTest
binaryfire Apr 13, 2026
b2ac7fe
BundleDetector caching tests
binaryfire Apr 13, 2026
95413a8
Coroutine isolation tests for InertiaState
binaryfire Apr 13, 2026
80b8ca5
Document request context behavior in porting guide
binaryfire Apr 13, 2026
37e4aea
CheckSsr artisan command
binaryfire Apr 13, 2026
7c2401f
CreateMiddleware generator command
binaryfire Apr 13, 2026
de0cef4
StartSsr artisan command
binaryfire Apr 13, 2026
b419b15
StopSsr command with Http facade replacing raw curl
binaryfire Apr 13, 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
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"Hypervel\\Hashing\\": "src/hashing/src/",
"Hypervel\\Horizon\\": "src/horizon/src/",
"Hypervel\\Http\\": "src/http/src/",
"Hypervel\\Inertia\\": "src/inertia/src/",
"Hypervel\\JsonSchema\\": "src/json-schema/src/",
"Hypervel\\JWT\\": "src/jwt/src/",
"Hypervel\\Log\\": "src/log/src/",
Expand Down Expand Up @@ -106,6 +107,7 @@
"src/collections/src/helpers.php",
"src/support/src/functions.php",
"src/support/src/helpers.php",
"src/inertia/src/helpers.php",
"src/testbench/src/functions.php"
]
},
Expand Down Expand Up @@ -213,6 +215,7 @@
"hypervel/hashing": "self.version",
"hypervel/horizon": "self.version",
"hypervel/http": "self.version",
"hypervel/inertia": "self.version",
"hypervel/jwt": "self.version",
"hypervel/log": "self.version",
"hypervel/macroable": "self.version",
Expand Down Expand Up @@ -299,6 +302,7 @@
"Hypervel\\Filesystem\\FilesystemServiceProvider",
"Hypervel\\Hashing\\HashingServiceProvider",
"Hypervel\\Http\\HttpServiceProvider",
"Hypervel\\Inertia\\InertiaServiceProvider",
"Hypervel\\JWT\\JWTServiceProvider",
"Hypervel\\Log\\Context\\ContextServiceProvider",
"Hypervel\\Log\\LogServiceProvider",
Expand Down
4 changes: 4 additions & 0 deletions docs/ai/porting.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,10 @@ All tests run inside coroutines by default. The `RunTestsInCoroutine` trait is o

These are primarily useful for DB operations or external service setup that needs coroutine context. Most ported Laravel tests won't need them.

#### Request Context in Tests

`request()` resolves from `RequestContext` — when no request exists in context (tests that don't make HTTP requests), each `request()` call creates a throwaway fallback instance. This means `request()->merge()` has no effect on subsequent `request()` calls. Replace `request()->merge(['key' => 'value'])` with `RequestContext::set(Request::create('/?key=value'))` to seed a stable request in context.

#### Static State and Test Cleanup

`AfterEachTestSubscriber` handles global static state cleanup between tests. It calls `flushState()` on framework classes that accumulate static state (Mockery, HandleExceptions, Carbon, Number, Eloquent Model, Paginator, etc.). **Never add cleanup for these in `tearDown()`** — it's already handled.
Expand Down
7 changes: 7 additions & 0 deletions src/contracts/src/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ public function handle(Request $request): Response;
*/
public function terminate(Request $request, Response $response): void;

/**
* Get the application's route middleware groups.
*
* @return array<string, array<int, mixed>>
*/
public function getMiddlewareGroups(): array;

/**
* Get the application instance.
*/
Expand Down
15 changes: 11 additions & 4 deletions src/http/src/Client/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ class Response implements ArrayAccess, Stringable
/**
* The decoded JSON response.
*/
protected array $decoded = [];
protected mixed $decoded = null;

/**
* Whether the response body has been decoded.
*/
protected bool $hasDecoded = false;

/**
* The custom decode callback.
Expand Down Expand Up @@ -73,8 +78,9 @@ public function body(): string
*/
public function json(?string $key = null, mixed $default = null): mixed
{
if (! $this->decoded) {
if (! $this->hasDecoded) {
$this->decoded = $this->decode($this->body());
$this->hasDecoded = true;
}

if (is_null($key)) {
Expand Down Expand Up @@ -111,15 +117,16 @@ public function object(): array|object|null
public function decodeUsing(?Closure $callback): static
{
$this->decodeUsing = $callback;
$this->decoded = [];
$this->decoded = null;
$this->hasDecoded = false;

return $this;
}

/**
* Decode the given response body.
*/
protected function decode(string $body, bool $asObject = false): array|object|null
protected function decode(string $body, bool $asObject = false): mixed
{
if ($this->decodeUsing instanceof Closure) {
return ($this->decodeUsing)($body, $asObject);
Expand Down
23 changes: 23 additions & 0 deletions src/inertia/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
The MIT License (MIT)

Copyright (c) Jonathan Reinink

Copyright (c) Hypervel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
5 changes: 5 additions & 0 deletions src/inertia/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Inertia.js Adapter for Hypervel

The Inertia.js server-side adapter for Hypervel, providing middleware, response factories, SSR support, Blade directives, and testing utilities.

Ported from the official [inertiajs/inertia-laravel](https://github.com/inertiajs/inertia-laravel) adapter with Swoole-specific optimisations: coroutine-safe per-request state isolation, worker-lifetime caching for immutable metadata, SSR timeouts with circuit breaker protection.
65 changes: 65 additions & 0 deletions src/inertia/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "hypervel/inertia",
"type": "library",
"description": "The Inertia.js adapter for Hypervel.",
"license": "MIT",
"keywords": [
"php",
"inertia",
"swoole",
"hypervel"
],
"authors": [
{
"name": "Albert Chen",
"email": "albert@hypervel.org"
},
{
"name": "Raj Siva-Rajah",
"homepage": "https://github.com/binaryfire"
}
],
"support": {
"issues": "https://github.com/hypervel/components/issues",
"source": "https://github.com/hypervel/components"
},
"autoload": {
"psr-4": {
"Hypervel\\Inertia\\": "src/"
},
"files": [
"src/helpers.php"
]
},
"require": {
"php": "^8.4",
"hypervel/console": "^0.4",
"hypervel/container": "^0.4",
"hypervel/context": "^0.4",
"hypervel/contracts": "^0.4",
"hypervel/foundation": "^0.4",
"hypervel/http": "^0.4",
"hypervel/macroable": "^0.4",
"hypervel/pagination": "^0.4",
"hypervel/routing": "^0.4",
"hypervel/session": "^0.4",
"hypervel/support": "^0.4",
"hypervel/testing": "^0.4",
"hypervel/view": "^0.4",
"symfony/console": "^8.0",
"symfony/process": "^8.0"
},
"config": {
"sort-packages": true
},
"extra": {
"hypervel": {
"providers": [
"Hypervel\\Inertia\\InertiaServiceProvider"
]
},
"branch-alias": {
"dev-main": "0.4-dev"
}
}
}
157 changes: 157 additions & 0 deletions src/inertia/config/inertia.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

declare(strict_types=1);

return [
/*
|--------------------------------------------------------------------------
| Server Side Rendering
|--------------------------------------------------------------------------
|
| These options configures if and how Inertia uses Server Side Rendering
| to pre-render the initial visits made to your application's pages.
|
| You can specify a custom SSR bundle path, or omit it to let Inertia
| try and automatically detect it for you.
|
| Do note that enabling these options will NOT automatically make SSR work,
| as a separate rendering service needs to be available. To learn more,
| please visit https://inertiajs.com/server-side-rendering
|
*/

'ssr' => [
'enabled' => (bool) env('INERTIA_SSR_ENABLED', true),

'runtime' => env('INERTIA_SSR_RUNTIME', 'node'),

'ensure_runtime_exists' => (bool) env('INERTIA_SSR_ENSURE_RUNTIME_EXISTS', false),

'url' => env('INERTIA_SSR_URL', 'http://127.0.0.1:13714'),

'ensure_bundle_exists' => (bool) env('INERTIA_SSR_ENSURE_BUNDLE_EXISTS', true),

// 'bundle' => base_path('bootstrap/ssr/ssr.mjs'),

/*
|--------------------------------------------------------------------------
| SSR Timeouts
|--------------------------------------------------------------------------
|
| Configure connection and read timeouts for SSR requests. These prevent
| coroutines from hanging indefinitely when the SSR server is unresponsive.
|
*/

'connect_timeout' => (int) env('INERTIA_SSR_CONNECT_TIMEOUT', 2),

'timeout' => (int) env('INERTIA_SSR_TIMEOUT', 5),

/*
|--------------------------------------------------------------------------
| SSR Backoff
|--------------------------------------------------------------------------
|
| When SSR fails, the worker will skip SSR for this many seconds before
| retrying. This acts as a circuit breaker to prevent flooding a dead
| SSR server with requests from every coroutine.
|
*/

'backoff' => (float) env('INERTIA_SSR_BACKOFF', 5.0),

/*
|--------------------------------------------------------------------------
| SSR Error Handling
|--------------------------------------------------------------------------
|
| When SSR rendering fails, Inertia gracefully falls back to client-side
| rendering. Set throw_on_error to true to throw an exception instead.
| This is useful for E2E testing where you want SSR errors to fail loudly.
|
| You can also listen for the Hypervel\Inertia\Ssr\SsrRenderFailed event
| to handle failures in your own way (e.g., logging, error tracking).
|
*/

'throw_on_error' => (bool) env('INERTIA_SSR_THROW_ON_ERROR', false),
],

/*
|--------------------------------------------------------------------------
| Pages
|--------------------------------------------------------------------------
|
| Set `ensure_pages_exist` to true if you want to enforce that Inertia page
| components exist on disk when rendering a page. This is useful for
| catching missing or misnamed components.
|
| The `paths` and `extensions` options define where to look for page
| components and which file extensions to consider.
|
*/

'pages' => [
'ensure_pages_exist' => false,

'paths' => [
resource_path('js/pages'),
],

'extensions' => [
'js',
'jsx',
'svelte',
'ts',
'tsx',
'vue',
],
],

/*
|--------------------------------------------------------------------------
| Testing
|--------------------------------------------------------------------------
|
| When using `assertInertia`, the assertion attempts to locate the
| component as a file relative to the `pages.paths` AND with any of
| the `pages.extensions` specified above.
|
| You can disable this behavior by setting `ensure_pages_exist`
| to false.
|
*/

'testing' => [
'ensure_pages_exist' => true,
],

/*
|--------------------------------------------------------------------------
| Expose Shared Prop Keys
|--------------------------------------------------------------------------
|
| When enabled, each page response includes a `sharedProps` metadata key
| listing the top-level prop keys that were registered via `Inertia::share`.
| The frontend can use this to carry shared props over during instant visits.
|
*/

'expose_shared_prop_keys' => true,

/*
|--------------------------------------------------------------------------
| History
|--------------------------------------------------------------------------
|
| Enable `encrypt` to encrypt page data before it is stored in the
| browser's history state, preventing sensitive information from
| being accessible after logout. Can also be enabled per-request
| or via the `inertia.encrypt` middleware.
|
*/

'history' => [
'encrypt' => (bool) env('INERTIA_ENCRYPT_HISTORY', false),
],
];
35 changes: 35 additions & 0 deletions src/inertia/src/AlwaysProp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Hypervel\Inertia;

class AlwaysProp
{
use ResolvesCallables;

/**
* The property value.
*
* Always included in Inertia responses, bypassing partial reload filtering.
*/
protected mixed $value;

/**
* Create a new always property instance. Always properties are included
* in every Inertia response, even during partial reloads when only
* specific props are requested.
*/
public function __construct(mixed $value)
{
$this->value = $value;
}

/**
* Resolve the property value.
*/
public function __invoke(): mixed
{
return $this->resolveCallable($this->value);
}
}
Loading