Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ed56e10
Add rate limiter package implementation plan
binaryfire Aug 4, 2026
b2a001d
Refine rate limiter package plan
binaryfire Aug 4, 2026
69ff7db
Finalize rate limiter package plan
binaryfire Aug 4, 2026
9e325b5
Add shared Swoole striped lock primitive
binaryfire Aug 5, 2026
aa99fb9
Use shared striped locks for Swoole cache state
binaryfire Aug 5, 2026
b188350
Refresh managers after application swaps
binaryfire Aug 5, 2026
8aebfe5
Reject parallel waits outside coroutines
binaryfire Aug 5, 2026
3c540f0
Add first-party rate limiter component
binaryfire Aug 5, 2026
693ec40
Ship rate limiter defaults in Testbench
binaryfire Aug 5, 2026
a948c63
Unify HTTP throttling on rate limiter stores
binaryfire Aug 5, 2026
d825a9b
Unify queue throttling on rate limiter stores
binaryfire Aug 5, 2026
8e96099
Move Fortify login throttling to the rate limiter
binaryfire Aug 5, 2026
8f760d8
Move exception throttling to the rate limiter
binaryfire Aug 5, 2026
53c91fb
Move Reverb limits to the rate limiter
binaryfire Aug 5, 2026
edaf632
Decouple Cache from rate limiting and adopt coordinator timers
binaryfire Aug 5, 2026
07de944
Use SHA-cached Lua for Redis limiters
binaryfire Aug 5, 2026
47853fa
Isolate configuration publishing tests
binaryfire Aug 5, 2026
0de73d9
Add rate limiter benchmark harness
binaryfire Aug 5, 2026
3168557
Document the rate limiter component
binaryfire Aug 5, 2026
b9b10f7
Document unified HTTP rate limiting
binaryfire Aug 5, 2026
d61e22d
Update rate-limited framework consumer documentation
binaryfire Aug 5, 2026
f7fd4cd
Record Hypervel rate limiter differences
binaryfire Aug 5, 2026
dc19347
Update rate limiter follow-up work
binaryfire Aug 5, 2026
908fd63
Reconcile the rate limiter implementation plan
binaryfire Aug 5, 2026
0450c36
Require consent before using subagents
binaryfire Aug 5, 2026
2c40cf6
Document the rate limiter package
binaryfire Aug 5, 2026
e21b50f
Document the prunable rate limiter store contract
binaryfire Aug 5, 2026
b73e86c
Use period terminology for leaky bucket limits
binaryfire Aug 5, 2026
b133907
Merge branch '0.4' into feature/rate-limiter
binaryfire Aug 5, 2026
5307b14
Skip rate limiter compression test without LZF
binaryfire Aug 5, 2026
2a50bcd
Merge remote-tracking branch 'origin/0.4' into feature/rate-limiter
binaryfire Aug 5, 2026
444f10d
Document rate limiter store cleanup requirements
binaryfire Aug 5, 2026
75e7f0c
Decouple Reverb message rate limiting
binaryfire Aug 5, 2026
7601632
Merge branch '0.4' into feature/rate-limiter
binaryfire Aug 6, 2026
8a49a43
Fix Redis integration database isolation
binaryfire Aug 6, 2026
5540c68
Fix Queue integration driver lifecycle
binaryfire Aug 6, 2026
41ec39d
Merge branch '0.4' into feature/rate-limiter
binaryfire Aug 6, 2026
6a58f5c
docs: plan first-class sliding window limits
binaryfire Aug 7, 2026
b1302f6
feat(rate-limiter): add sliding window policy
binaryfire Aug 7, 2026
e6551c4
feat(rate-limiter): implement sliding windows in PHP stores
binaryfire Aug 7, 2026
06bf4ac
feat(rate-limiter): optimize sliding windows for Redis
binaryfire Aug 7, 2026
09a163f
test(rate-limiter): cover sliding windows across consumers
binaryfire Aug 7, 2026
24b003d
perf(rate-limiter): benchmark sliding window paths
binaryfire Aug 7, 2026
d81f5bb
docs(rate-limiter): document sliding window limits
binaryfire Aug 7, 2026
d18140b
docs: align rate limiter package plan with sliding windows
binaryfire Aug 7, 2026
295114e
Merge remote-tracking branch 'origin/0.4' into feature/rate-limiter
binaryfire Aug 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
5 changes: 3 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@
# REDIS_DB=1
# REDIS_TEST_DB_MIN=1
# REDIS_TEST_DB_MAX=14
# Database 15 is excluded from the worker range. Reverb state and secondary
# Redis tests share it safely because those tests use unique keys and never flush it.
# Database 15 is excluded from the worker range for Reverb integration servers
# and tests that explicitly request the secondary database. InteractsWithRedis
# remaps every named application connection, including reverb, to its worker database.
# REDIS_REVERB_DB=15
# REDIS_TEST_SECONDARY_DB=15

Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/redis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,12 @@ jobs:
# Cap ParaTest's auto-detected workers: database 1 is reserved via
# REDIS_TEST_SECONDARY_DB, leaving 15 worker databases in the 0-15 range.
run: |
vendor/bin/paratest --max-processes=15 tests/Integration/Auth
vendor/bin/paratest --max-processes=15 tests/Integration/Auth/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/Cache/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/Horizon
vendor/bin/paratest --max-processes=15 tests/Integration/Http/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/Queue/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/RateLimiter/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/Redis

valkey_9:
Expand Down Expand Up @@ -125,7 +128,10 @@ jobs:
# Cap ParaTest's auto-detected workers: database 1 is reserved via
# REDIS_TEST_SECONDARY_DB, leaving 15 worker databases in the 0-15 range.
run: |
vendor/bin/paratest --max-processes=15 tests/Integration/Auth
vendor/bin/paratest --max-processes=15 tests/Integration/Auth/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/Cache/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/Horizon
vendor/bin/paratest --max-processes=15 tests/Integration/Http/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/Queue/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/RateLimiter/Redis
vendor/bin/paratest --max-processes=15 tests/Integration/Redis
14 changes: 8 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ When working on Hypervel, start from this frame:
- Per-request state must live in coroutine-scoped storage (CoroutineContext), not process-global state.
- Laravel source is the default parity reference, but Laravel internals often assume per-request bootstrap and are not optimized to take advantage of static caching of immutable state.
- Hyperf source can be useful for Swoole/coroutine behavior, but Hyperf container/config/listener patterns are not the target architecture.
- Laravel's rate limiter lives under `Illuminate\Cache`; Hypervel's canonical implementation is the dedicated `hypervel/rate-limiter` package under `Hypervel\RateLimiter`. It uses typed policies and dedicated atomic stores and has no `Hypervel\Cache` alias or primitive counter API. Use this package directly when porting rate-limited Laravel code.

## Repository Layout and Commands

Expand All @@ -33,7 +34,7 @@ Key paths:
| `src/testbench/` | Hypervel's testbench package (port of `orchestra/testbench`). Contains `TestCase`, attributes (`WithConfig`, `WithMigration`), and bootstrap logic. Part of the monorepo, not a vendor dependency. |
| `src/testbench/hypervel/` | Committed Hypervel app skeleton. On bootstrap, testbench clones this to a disposable temp directory (`/tmp/hypervel-components-testbench-{token}-{pid}/`) and points `BASE_PATH` at the clone — tests that write files under `BASE_PATH` (generated providers, migrations, fixtures, etc.) hit the temp copy, not this committed path. The clone is deleted on shutdown and stale copies from crashed runs are cleaned up. Testbench also exports `TESTBENCH_BASE_PATH` so subprocesses can locate the active runtime. |
| `src/testbench/workbench/` | Committed shared test fixtures (NOT cloned). Subdirs are psr-4-mapped from the monorepo root as `Workbench\App\*`, `Workbench\Database\Factories\*`, `Workbench\Database\Seeders\*` so multiple tests can reuse the same models/factories/seeders without redefining them. Not the runtime app — that's the disposable clone of `src/testbench/hypervel/`. |
| `docs/ai/` | Supplementary agent guides, including `porting-hyperf.md` (Hyperf conversion mechanics) and `differences-vs-laravel.md` (user-facing Laravel differences). |
| `docs/ai/` | Supplementary agent guides, including `porting-hyperf.md` (Hyperf conversion mechanics). |
| `docs/todo.md` | Tracked gaps and improvements worth doing. |

### Running tests
Expand Down Expand Up @@ -119,6 +120,7 @@ The Working rules and the Avoid overengineering rules apply to all work in this

### Working rules

- **Never use subagents without explicit user consent** — Do not spawn or delegate work to subagents unless the user explicitly requests or approves their use.
- **Avoid bulk modification tools** — tools like `sed` and `replace_all` often have unwanted side effects. Never use bulk modification tools without explicit user approval; prefer manual edits. When approved, run them in multiple passes that each target long, exact, case-sensitive strings to avoid accidental changes.
- **One file at a time** — never work on multiple files simultaneously. This governs manual editing; package-manager and formatter runs may touch multiple files.
- **Never use Write to overwrite files** — always use Edit for targeted updates.
Expand Down Expand Up @@ -200,8 +202,6 @@ Build complete, long-term solutions, not MVPs or local workarounds. A broad chan

Hypervel's container keeps Laravel's API surface — `bind()`, `singleton()`, `scoped()`, `instance()`, aliases, contextual bindings — with resolution adapted for long-lived Swoole workers. `make()` and `get()` resolve identically; `get()` is just the PSR-compliant exception wrapper. Use `make()`, and use it instead of array access too: `offsetGet()` always returns `mixed`, while `make()` carries class-string generics phpstan can follow, `make()` can take parameters, and `$app[$key] = $value` is a hidden `bind()`. Converting `$app['...']` in ported code to `make()` is an approved modernization (see Policy under Porting Packages). `Container::getInstance()` auto-creates via `??= new static()`, so it always returns a container.

A user-facing summary of these differences lives in `docs/ai/differences-vs-laravel.md` — keep it consistent with this section when container behavior changes.

### Resolution semantics vs Laravel

The critical difference: **unbound concrete classes are auto-singletoned**. In Laravel, `make()` on a class with no binding builds a fresh instance every call. In Hypervel, the first resolution caches the instance (in `$autoSingletons`) for the worker lifetime — in Swoole's long-running process model services are stateless singletons by design, and re-creating them on every resolution wastes CPU and memory. Explicit bindings override this (bound classes follow their binding type), and `SelfBuilding` classes are excluded.
Expand Down Expand Up @@ -376,7 +376,7 @@ Test supported public behavior, meaningful branches, verified regressions, and r

### Directory layout

All tests live in `tests/{PackageName}/` (PascalCase). Tests that require external services go in `tests/Integration/{PackageName}/` — see Integration tests below.
All tests live in `tests/{PackageName}/` (PascalCase). Tests that require external services go in `tests/Integration/{PackageName}/` — see Integration tests below. When only some integration tests for a package require one service, group them in `tests/Integration/{PackageName}/{ServiceName}/`. When every integration test for the package requires that service, keep them directly in the package directory.

Package-specific tests that require one database driver go in `tests/Integration/{PackageName}/Database/{Postgres|MySql|MariaDb|Sqlite}/`. The database workflows discover these directories by convention.

Expand Down Expand Up @@ -644,6 +644,8 @@ If a test fails with a type error, the source code type may be wrong — not the

Tests that require external services (databases, Redis, HTTP servers, search engines) that can't run in every environment go in `tests/Integration/{PackageName}/`. The exception is tests that call freely-available external APIs (e.g., the Guzzle tests hitting the public Pokemon API) — those can stay in regular `tests/` since they need no local service configuration.

Service workflows enumerate their test directories explicitly. Adding a service-specific directory requires adding it to the matching workflow; using the service trait provides isolation and skip behavior but does not make CI discover the test.

#### External service test traits

Integration tests that use an external service must use that service's test trait.
Expand Down Expand Up @@ -677,8 +679,8 @@ Each integration group has its own workflow file in `.github/workflows/`:
| Workflow | Runs | Directory |
|----------|------|-----------|
| `engine.yml` | HTTP test servers | `tests/Integration/Engine`, `tests/Integration/HttpServer` |
| `databases.yml` | MySQL, MariaDB, PostgreSQL, SQLite | `tests/Integration/Database` |
| `redis.yml` | Redis, Valkey | `tests/Integration/Cache/Redis`, `tests/Redis/Integration` |
| `databases.yml` | MySQL, MariaDB, PostgreSQL, SQLite | `tests/Integration/Database`, `tests/Integration/*/Database/*` |
| `redis.yml` | Redis, Valkey | `tests/Integration/Auth/Redis`, `tests/Integration/Cache/Redis`, `tests/Integration/Horizon`, `tests/Integration/Http/Redis`, `tests/Integration/Queue/Redis`, `tests/Integration/RateLimiter/Redis`, `tests/Integration/Redis` |
| `scout.yml` | Meilisearch, Typesense | `tests/Integration/Scout/*` |

When adding integration tests that need a new service, either add them to an existing workflow or create a new one. The workflow must spin up the service container and set the appropriate env vars.
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"Hypervel\\Process\\": "src/process/src/",
"Hypervel\\Prompts\\": "src/prompts/src/",
"Hypervel\\Queue\\": "src/queue/src/",
"Hypervel\\RateLimiter\\": "src/rate-limiter/src/",
"Hypervel\\Redis\\": "src/redis/src/",
"Hypervel\\Reverb\\": "src/reverb/src/",
"Hypervel\\Routing\\": "src/routing/src/",
Expand Down Expand Up @@ -261,6 +262,7 @@
"hypervel/process": "self.version",
"hypervel/prompts": "self.version",
"hypervel/queue": "self.version",
"hypervel/rate-limiter": "self.version",
"hypervel/redis": "self.version",
"hypervel/reflection": "self.version",
"hypervel/routing": "self.version",
Expand Down Expand Up @@ -349,6 +351,7 @@
"Hypervel\\Grpc\\GrpcServiceProvider",
"Hypervel\\Pipeline\\PipelineServiceProvider",
"Hypervel\\Queue\\QueueServiceProvider",
"Hypervel\\RateLimiter\\RateLimiterServiceProvider",
"Hypervel\\Redis\\RedisServiceProvider",
"Hypervel\\Reverb\\ReverbServiceProvider",
"Hypervel\\Routing\\RoutingServiceProvider",
Expand Down
Loading