The honest, feature-by-feature mapping between Laravel 13.x and Suprnova. Use this when you're asking "does Suprnova have X?" and want a yes/no/where answer in one row.
Sections mirror the Laravel docs index so a Laravel developer can scan top-to-bottom. Within each section the columns are always the same:
| Laravel | Suprnova | Status | Notes / link |
|---|
The Status column uses four values:
| Symbol | Meaning |
|---|---|
| shipped | Same surface, same behaviour (often same method names) |
| diverged | Same job, different shape because Rust makes a better choice possible |
| not yet | Genuinely planned, not yet on disk |
| by design no | Won't ship - explanation in the Notes column |
The relevant chapter (where one exists) is linked from the Notes column.
This is a live map. Suprnova ships every Laravel 13.x surface across the 30 documented domains; the gaps listed below are the real, current gaps as of the shipped framework.
Architecture concepts
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
| Request Lifecycle | Application → Server → handle_request chain |
shipped | Lifecycle |
| Service Container | Container + App facade, three-layer (task / thread / global) |
diverged | Task-local for per-request, thread-local for tests - Container |
| Service Providers | bootstrap() function + #[service], #[policy], #[command], observer macros |
diverged | No registration class - bootstrap is one function; macros use inventory for compile-time registration. Bootstrap |
| Facades | Static App::get, Cache::*, Mail::*, Auth::*, Storage::*, Queue::*, Bus::*, Event::*, Notification::*, Gate::*, Schedule::*, DB::*, Vector::* |
shipped | Same call shape; the facades are real types, not aliases |
| Contracts | Traits - Mailer, KeyValueStore, Hasher, Channel, VectorDriver, Evaluator, PaymentProvider, etc. |
shipped | All public seams live on traits; bind by trait, swap implementations freely |
Getting started
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
| Installation | cargo install --git …suprnova-cli then suprnova new <name> |
shipped | Installation |
| Configuration | Typed config via #[derive(Config)] + Config::register |
diverged | Compile-time typed instead of array bags. Configuration |
| Agentic Development (AI) | No first-class AI SDK in framework | by design no | Use the crates you'd use anyway (async-openai, anthropic-rs, tokenizers, etc.) under App::bind(Arc<dyn YourLlm>) |
| Directory Structure | src/{actions,bootstrap,controllers,middleware,models,routes} |
shipped | Same intent, Rust-idiomatic layout. Structure |
| Frontend | Inertia v3 over Svelte 5 / React 19 / Vue 3.5 | shipped | Frontend, Pages, TS Types |
| Starter Kits | Nebula (auth) and Pulsar (full product site), plus the plain suprnova new scaffold |
shipped | Two kits ship today - Nebula is the Breeze equivalent; Pulsar adds docs, blog, community, and RBAC. Starter Kits |
| Deployment | Single binary; Docker / Railway / DO / Hetzner recipes | diverged | One artifact, not a PHP runtime + opcache + FPM. Deployment |
The basics
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
| Route definitions | routes! macro + get! / post! / put! / patch! / delete! / any! / head! / options! / fallback! / ws! |
shipped | Routing |
| Route parameters | {id} path params + req.param("id") |
shipped | Optional params via {id?}; constraints via where!() |
| Route names | .name("posts.show") on the route + url("posts.show", &[("id", "42")]) |
shipped | URL Generation |
| Route groups | group! macro with .prefix() / .middleware() / .name() / .controller() |
shipped | Group middleware is flattened onto each route at register time |
| Resource routes | resource!("posts", PostController) registers the 7 standard routes |
shipped | apiResource!, only(...), except(...) all supported |
| Signed URLs | sign_url(...), sign_route(...), verify_signature(...) |
shipped | HMAC-SHA256 with APP_KEY |
| Route model binding | #[handler] extracts Post from {post} via RouteBinding impl |
shipped | AutoRouteBinding derive auto-implements for #[suprnova::model] types |
| Rate limiting | throttle:60,1 middleware + RateLimiter::for_signature |
shipped | Rate Limiting |
| Middleware | impl Middleware trait; register globally or per-route |
shipped | Middleware |
| Middleware groups + aliases | register_middleware_group, register_middleware_alias |
shipped | Look up by string name in routes |
| CSRF Protection | CsrfMiddleware + csrf_token() / csrf_field() / csrf_meta_tag() |
shipped | Origin policy enforces same-origin POST. CSRF |
| Controllers | #[handler] pub async fn show(req: Request) -> Response |
shipped | Controllers are modules of free functions, not classes. Controllers |
| Single-action controllers | A handler is already a single function; group into modules | shipped | The Rust convention - no __invoke ceremony |
| Requests | Request struct with .input(), .param(), .query(), .header(), .cookie(), .json(), .file(), etc. |
shipped | Requests |
| Form Requests | #[derive(Data, Validate, FormRequest)] |
shipped | Validation runs as you extract |
| File uploads | req.file("avatar")? returns UploadedFile; streaming multipart with size + part caps |
shipped | Auto-spill to tempfile above threshold |
| Responses | HttpResponse builders + json!() / text!() / Redirect::to / view |
shipped | Responses |
| Views (Blade) | Server-rendered Inertia pages (Svelte/React/Vue) - no Blade equivalent | diverged | Inertia is the view layer. Use Pages instead of Blade |
| Asset Bundling (Vite) | Vite 8 ships in every scaffold; suprnova serve runs Vite + backend together |
shipped | Manifest reading + HMR auto-wired |
Static assets (public/, served by the web server in Laravel) |
StaticFiles::public() in-process fallback handler serving public/ at the web root |
shipped | StaticFiles::from_dir(...) + cache_control(...); no separate web server needed |
| URL Generation | url("posts.show", &[…]), route("posts.show", …), redirect(...), redirect_to(...) |
shipped | URL Generation |
| Session | session(), session_mut(), flash bag via req.flash() |
shipped | DB-backed via DatabaseSessionDriver; cookie-backed by default. Session |
| Validation | #[derive(Validate)] + 17 built-in rules + Rule/AsyncRule traits |
shipped | Async rules (e.g. Unique) hit the DB. Validation |
| Error Handling | FrameworkError, AppError, HttpError trait, panic boundary in execute_chain_safely |
shipped | Error Handling, Error Model |
| Logging | tracing subscriber with structured fields, LogFormat (json / pretty / compact) |
diverged | One log line is a JSON document; request_id always present. Logging |
| Abort helpers | abort_if(cond, status, msg), abort_unless(...), abort_with(status, msg) |
shipped | Same shape as Laravel's abort_if family |
Digging deeper
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
| Artisan Console | Per-app console binary built from #[command] + #[derive(Command)] |
shipped | Console. cargo run --bin console <subcommand> |
| Tinker (REPL) | No REPL | by design no | Write a one-off cargo run --bin xxx script or a #[suprnova_test] |
| Broadcasting | BroadcastHub + Channel / PrivateChannel / PresenceChannel + Broadcastable |
shipped | sea-streamer fanout for multi-node. Broadcasting |
| Cache | Cache::get/put/forget/remember/rememberForever/increment/... + InMemoryCache, RedisCache |
shipped | Atomic ops + tagged cache + cache locks (LockGuard). Cache |
| Collections | eloquent::Collection<M> with Laravel-shape methods |
shipped | Deref<Target = Vec<M>> so existing Vec idioms still work. Collections |
| Concurrency | Tokio everywhere - tokio::spawn, tokio::join!, tokio::select! |
shipped | The whole framework is async. The Laravel Concurrency::run([...]) facade doesn't ship; Tokio is the answer |
| Context | Context::put / Context::get / ContextStore + auto-injection into queue / mail / events |
shipped | Context |
| Contracts | All public seams are traits | shipped | See the "Architecture / Contracts" row above |
| Events | EventFacade::dispatch(e).await?, #[derive(Event)], EventDispatcher, queued listeners, subscribers |
shipped | Events |
| File Storage | Storage::disk("local"|"s3"|"azblob"|"gcs"|"memory") over OpenDAL |
shipped | Same put/get/delete/copy/move/exists/url surface. Path-traversal protection built in. Filesystem |
| Helpers | Equivalents are in their home modules (no kitchen-sink helpers.md) |
diverged | E.g. URL helpers live in urls.md, string helpers in std/heck, array helpers in std::collections - Rust does this with crates, not a global namespace |
| HTTP Client | Http::get/post/... builder + Http::fake(...) for tests |
shipped | Auto-records requests; assert_sent / assert_not_sent. HTTP Client |
| Localization | Lang::get / get_with / try_get / has + the __!("key", name: value) macro over Fluent .ftl catalogs in lang/<locale>/, LocaleMiddleware detection, translated validation messages, ICU4X formatting |
shipped | The same catalog is served to the browser at /_suprnova/lang/<locale>.ftl and typed by generate-types. Localization |
Mail::to(...).send(MyMail { ... }).await? + drivers smtp/ses/mailgun/postmark/sendgrid/resend/log/memory |
shipped | Mailable trait + Tera-rendered HTML/text bodies. Mail |
|
| Notifications | Notify::send(&user, notif).await? + channels mail/database/broadcast/webpush |
shipped | Notifiable trait + Notification per channel. Notifications, Web Push |
| Package Development | Workspace adapter crates (e.g. suprnova-payments-stripe) |
shipped | Same shape as Laravel packages: depend on the framework, bind into the container, expose macros if needed |
| Processes (running shell commands) | tokio::process::Command from the stdlib |
by design no | No facade - Tokio's API is already the right shape |
| Queues | Queue::push(job).await? + drivers sync/memory/database/redis/null, batches, chains, JobMiddleware, FailedJobStore |
shipped | Queues |
| Rate Limiting | RateLimiter::for_signature(...), ThrottleRequestsMiddleware, RateLimitMiddleware |
shipped | Sliding window via SlidingWindowConfig. Rate Limiting |
| Search (Scout) | No first-party full-text search adapter | not yet | Vector search ships today via Vector; keyword-search Scout-equivalent is planned |
| Strings (helpers) | heck crate (case conversions), std::str, regex |
diverged | Same crates the rest of the Rust ecosystem uses; no Str::camel($x) global |
| Task Scheduling | Schedule::call/command/task + #[derive(Task)] + cron syntax + schedule:run worker |
shipped | Scheduling |
| Idempotency keys | Idempotency::remember(key, ttl, body) - Stripe-style replay protection |
shipped | Caller namespaces the key with the route + user / business identity. Idempotency |
| Request timeout | TimeoutMiddleware configurable per route |
shipped | Rust-native - abort the in-flight future, free the worker. Timeout |
| Feature Flags (Pennant) | Feature + Evaluator + FeatureMiddleware + admin CRUD |
shipped | Sub-second propagation via FeatureSync trait. Feature Flags |
| Observability (Pulse) | OpenTelemetry via init_telemetry, Metrics, tracing everywhere |
diverged | OTel is the lingua franca for Rust observability - point your collector at the binary. Observability |
| Telescope (debug dashboard) | No equivalent yet | not yet | Deferred to v2+; the framework's tracing + OTel output covers most diagnostic needs |
| Pulse (perf dashboard) | No equivalent yet | not yet | Same as Telescope - surface metrics with your existing observability stack until a dashboard ships |
| Vector search | Vector::driver("memory"|"qdrant"|"pinecone"|"mariadb") |
shipped | No "Postgres pgvector only" gatekeeping. Vector Search |
Suprnova-exclusive (no Laravel equivalent)
| Suprnova | What it is | Notes / link |
|---|---|---|
ws!() macro + WebSocket handlers |
Typed WS routes that share the router + middleware stack | WebSockets |
| Server-Sent Events | SseEvent + HttpResponse::sse(...) |
SSE |
| Workflows | Long-running stateful work with retries, sleep, step boundaries | Workflows |
| Supervisors | Supervisor trait with panic-catch auto-restart for long-lived tokio tasks |
Supervisors |
| Web Push (VAPID) | Browser push notifications as a first-class channel | Web Push |
| Multi-connection read/write split | READ_REPLICA_CONNECTION_NAME + DB::on("read").select(...) |
Database |
| HTTP/2 + WebSocket on the same socket | hyper.with_upgrades() in Server::run |
Lifecycle |
| Markdown content + docs pipeline | MarkdownRenderer (sanitised comrak → syntect → ammonia) + build_docs(DocsBuildConfig) → searchable DocsCatalog of DocsChapters |
Heading extraction + slugify_heading; powers Markdown docs / blog with no separate static-site generator |
Security
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
| Authentication | Auth::user/check/login/logout/attempt, Authenticatable trait, Guard per name |
shipped | Authentication |
| Multiple guards | Guard registered by name (web, api, …) via AuthManager |
shipped | SessionGuard, TokenGuard, custom impls |
| User providers | EloquentUserProvider<U>, DatabaseUserProvider, custom via UserProvider trait |
shipped | Auth Flows |
| Email Verification | EmailVerification + EnsureEmailVerifiedMiddleware + EmailVerificationMail; MustVerifyEmail contract on the user model |
shipped | Provider-backed (no torii) - Auth Flows |
| Password Reset | PasswordReset + PasswordResetMail + PasswordChangedMail; CanResetPassword contract on the user model |
shipped | Provider-backed (no torii) - Auth Flows |
| Brute-force throttling | BruteForce + LoginThrottleMiddleware |
shipped | Per-IP + per-user accounting |
| Two-Factor (TOTP) | TwoFactor + TwoFactorChallengeMiddleware + TwoFactorUser trait |
shipped | Recovery codes + replay protection |
| Remember-me | Long-lived signed cookie via SessionGuard |
shipped | Framework-owned auth::remember: DB-row + bcrypt + single-use rotation |
| OAuth (Socialite) | Via the vendored torii_integration fork (Google / GitHub / Apple etc.) |
shipped | Authentication |
| Sanctum (API tokens) | TokenGuard + DB-backed tokens via torii |
diverged | Token model + bearer middleware ship; no separate Sanctum API surface |
| Passport (OAuth server) | Not yet | not yet | If you need an OAuth provider, run a dedicated identity service (Keycloak, Hydra) behind Suprnova |
| Fortify (auth backend) | Replaced by auth_flows module + auth_flows::* types |
shipped | Same job; no headless-vs-headed split needed because the frontend is Inertia |
| Authorization (Policies / Gates) | Gate::allows/denies + #[policy] impl PostPolicy + Authorizable trait + macro registration |
shipped | Authorization |
| Roles & permissions (spatie/laravel-permission) | HasRoles trait + roles / permissions / role_has_permissions tables (CreateRbacTables) + RoleMiddleware / PermissionMiddleware (fail-closed) |
shipped | First-party, not a community package. create_role / give_permission_to_role / assign_role_to_model helpers; layers on top of Gate/Policy. Authorization |
| Encryption | Crypt::encrypt/decrypt + CryptPurpose AAD binding |
shipped | AES-256-GCM, key rotation via APP_KEY_PREVIOUS. Encryption |
| Hashing | hash::* + BcryptHasher, Argon2idHasher, Argon2iHasher, needs_rehash, is_hashed, verify |
shipped | Bcrypt default; argon2id available. Hashing |
Database
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
| DB::table('users')->where(...)->get() | DB::table("users").db_where("id", "=", 1).get().await? |
shipped | Database, Queries |
| Multiple connections | DB::on("read") + ConnectionRegistry |
shipped | Read/write split first-class |
| Transactions | DB::transaction(|tx| async move { ... }).await? |
shipped | Savepoints + retry-on-deadlock |
| Query events | QueryListener + QueryExecuted event |
shipped | DB::listen(|q| { ... }) |
| Raw expressions | DB::raw("..."), DB::select("...", &[...]) |
shipped | Parameter binding required (no string interpolation) |
| Postgres / MySQL / SQLite | All three first-class via SeaORM | shipped | URL detection in database::config::database_type() |
| MariaDB | First-class as its own option (vector + JSON + temporal) | diverged | Treated separately because of multi-paradigm features Laravel ships as Postgres-only |
| Redis | Used by drivers (cache/queue/rate-limit) - no separate Redis::* facade |
diverged | Reach for redis crate directly when you need ad-hoc commands; cache/queue/rate-limit cover 95% of typical use |
| MongoDB | No first-party adapter yet | not yet | Use mongodb crate directly via App::bind |
| Query Builder | Builder<M> with db_where / or_where / where_in / where_between / where_null / where_has / with / with_count / order_by / group_by / having / paginate / etc. |
shipped | Queries |
| Pagination | LengthAwarePaginator, Paginator (simple), CursorPaginator |
shipped | All three serialise to Laravel-shape JSON. Pagination |
| Migrations | #[derive(DeriveMigrationName)] struct M; + up/down + Migrator |
shipped | Run via suprnova migrate/migrate:rollback/migrate:status/migrate:fresh. Migrations, CLI Migrations |
| Seeders | Seeder trait + db:seed subcommand |
shipped | Per-model factories. Seeding |
Eloquent ORM
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
class User extends Model |
#[suprnova::model(table = "users")] struct User { ... } |
shipped | The struct IS the SeaORM Model. Eloquent |
| Find / first / get | User::find(id), User::query().first(), User::all(), Builder::get |
shipped | All async |
| Create / update / delete | User::create(attrs), user.update(attrs), user.delete() |
shipped | attrs! { name: "...", email: "..." } macro for partial attrs |
| Mass assignment guards | #[model(fillable = [...])] / #[model(guarded = [...])] + unguarded || { ... } scope |
shipped | prevent_silently_discarding_attributes() for strict mode |
| Soft deletes | #[model(soft_deletes)] auto-injects deleted_at + SoftDeletes trait |
shipped | with_trashed(), only_trashed(), restore(), force_delete() |
| Prunable / MassPrunable | #[prunable] impl Prunable for User { ... } + model:prune worker |
shipped | Cascade-pinned to relations |
| Timestamps | Auto created_at/updated_at if columns are present |
shipped | Disable via #[model(timestamps = false)] |
| Primary key types | i64 default; UUID / ULID via #[model(unique_id = "uuid")] or unique_id = "ulid" |
shipped | Auto-generates id on insert |
| Local scopes | #[scopes(User)] impl User { fn active(b: &mut Builder<User>) { ... } } |
shipped | Method dispatch on Builder<M> |
| Global scopes | impl GlobalScope for ActiveOnly { ... } + register |
shipped | Stripped via Builder::without_global_scope |
| Relationships (11 kinds) | HasOne, HasMany, BelongsTo, BelongsToMany, HasOneThrough, HasManyThrough, MorphOne, MorphMany, MorphTo, MorphToMany, MorphedByMany |
shipped | Per-family morph enum. Relationships |
| Eager loading | User::query().with(&["posts", "posts.comments"]).get() |
shipped | EagerLoadDispatch is sealed; only macro-generated relations can implement it |
| Lazy loading prevention | prevent_silently_discarding_attributes(true) |
shipped | Same shape as Laravel's preventLazyLoading |
| Aggregates on relations | with_count("posts"), with_sum("orders", "total"), with_avg, with_min, with_max |
shipped | Single subquery per aggregate |
whereHas / whereDoesntHave |
where_has("posts", |q| q.db_where("published", "=", true)) |
shipped | Correlated EXISTS engine |
loadMissing |
user.load_missing(&["posts"]).await? |
shipped | Operates collection-wide |
| Cloning a record | user.replicate() / user.replicate_into::<OtherType>() |
shipped | Dispatches Replicating event |
| Touching parent timestamps | #[model(touches = ["post"])] |
shipped | without_touching || { ... } to skip |
| Observers | impl Observer<User> + #[suprnova::observer(User)] |
shipped | 16 lifecycle events |
| 16 lifecycle events | Created, Creating, Saving, Saved, Updating, Updated, Deleting, Deleted, Trashed, Restoring, Restored, Retrieved, Replicating, ForceDeleting, ForceDeleted, Pruning |
shipped | Per-model events::* submodule. EventResult::cancel(_) short-circuits with a 400 |
| Mutators / Accessors | #[accessor] fn full_name(&self) -> String { ... } + #[mutator] fn set_password(&mut self, v: String) |
shipped | Mutators |
| Casts (22 built-in) | casts! { AsString, AsInt, AsFloat, AsBool, AsJson, AsArray, AsArrayObject, AsObject, AsCollection, AsDate, AsDateTime, AsImmutableDate, AsImmutableDateTime, AsOptionalDateTime, AsTimestamp, AsDecimal, AsEnum<E>, AsEncrypted, AsEncryptedObject, AsEncryptedArray, AsEncryptedCollection, AsHashed } |
shipped | Implement Cast for custom |
| Collections | Collection<M> with pluck, filter, map, each, chunk, groupBy, keyBy, sort_by, where_, first, last, count, is_empty, to_array and Laravel friends; Deref<Target = Vec<M>> so all Vec idioms keep working |
shipped | Collections |
| API Resources | #[derive(Resource)] + IntoJsonResource + JsonApiResponse + fieldsets + includes |
shipped | JSON:API shape + Laravel-style resource shape both available. API Resources |
| Serialization | #[model(hidden = [...], visible = [...], appends = [...])] |
shipped | Same control over which attributes serialise. Serialization |
| Factories | #[derive(Factory)] struct UserFactory + UserFactory::new().count(5).create().await? (or UserFactory::times(5).create_many().await?) |
shipped | Sequence for cycling values. Factories |
| Lifecycle: chunking / lazy / cursor | Builder::chunk(n, |page| async { ... }), lazy(), cursor() |
shipped | Memory-bounded iteration over large tables |
| Pessimistic locking | Builder::lock_for_update(), shared_lock() |
shipped | Inside a transaction |
whereJsonContains family |
Available via SeaORM's column expressions (driver-aware) | shipped | The exact spelling differs per backend; helpers ship for the common cases |
Pagination
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
LengthAwarePaginator |
LengthAwarePaginator (page + total + per_page + last_page) |
shipped | Builder::paginate(n).await? |
Paginator (simple) |
Paginator (page + per_page + has_more, no count) |
shipped | Builder::simple_paginate(n).await? |
CursorPaginator |
CursorPaginator (opaque cursor token + direction) |
shipped | Builder::cursor_paginate(n).await?; deterministic for infinite scroll |
| Inertia integration | IntoInertiaScroll trait + ScrollMetadata |
shipped | Wires straight into Inertia's WhenVisible / merge |
AI (Laravel ships native today; we don't gatekeep)
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
| AI SDK | No first-party AI SDK | by design no | Bring the crate you already use (async-openai, anthropic-sdk, ollama-rs, tokenizers, etc.) and bind under App |
| MCP (Model Context Protocol) | No first-party MCP server adapter | by design no | The Rust MCP crates (mcp-rs, mcp-sdk-rust) sit cleanly under the existing routing / supervisor surface |
| Boost (Laravel coding agent) | n/a | by design no | Out of framework scope |
Testing
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
php artisan test |
cargo test |
shipped | Testing |
| Pest / PHPUnit style | #[suprnova_test] (async-aware) + expect!() Jest-like assertions + describe!() / test!() BDD macros |
shipped | All three work interchangeably |
| Feature tests (HTTP) | Drive handle_request(router, registry, req) in-process - no socket open |
shipped | HTTP Tests |
| Console tests | Run dispatch_argv(["console", "..."]) and assert |
shipped | Same shape as HTTP tests for the console binary |
| Browser tests (Dusk) | n/a in framework - use Playwright / WebdriverIO / gstack agent browser |
by design no | Cross-language tooling already exists; we don't reinvent it |
| Database tests | TestDatabase::fresh::<Migrator>() + per-test rollback |
shipped | Database Tests |
| Mocking & fakes | Per-facade fakes: MailFake, NotifyFakeGuard, EventFakeGuard, Queue::fake, Bus::fake, Http::fake, Storage::fake |
shipped | Recorded calls + assertion helpers. Mocking |
| Time travel | tokio::time::{pause, advance, resume} from the stdlib runtime |
shipped | Don't ship our own - Tokio's API already does it |
| Container isolation | TestContainer::fake(|tc| tc.bind(...)) - thread-local |
diverged | Parallel-safe by construction. Container |
Payments (Laravel's Cashier; ours is provider-generic)
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
| Cashier (Stripe) | suprnova-payments-stripe adapter crate behind generic Payment / Subscription / CustomerStore / WebhookHandler traits |
diverged | Generic surface, concrete adapter. Payments, Stripe Adapter |
| Cashier (Paddle) | suprnova-payments-paddle adapter |
diverged | Merchant-of-Record flow + no direct Payment impl (Paddle owns the gateway). Paddle Adapter |
| Custom provider | Implement PaymentProvider + SessionPayload + WebhookHandler |
shipped | Provider Guide |
| Inertia checkout components | Documented dispatch loops for Svelte / React / Vue against SessionPayload.flow |
shipped | Payments Frontend. Ready-made billing pages are a planned starter-kit addition (Starter Kits) |
| Subscription lifecycles | Subscription::subscribe / update / cancel / get (where the provider supports them) |
shipped | NotSupported returned where the provider doesn't (e.g. Paddle subscribe and price-set replacement) |
| Webhook idempotency | payments_webhook_events mirror table with UNIQUE(provider, provider_event_id) |
shipped | Stripe-style replay protection |
| Mirror tables | payments_customers, payments_payment_methods, payments_subscriptions, payments_subscription_items, payments_transactions, payments_webhook_events |
shipped | provider_metadata JSONB column on each for adapter-specific fields |
Frontend (Laravel has Blade + starter kits; we have Inertia)
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
| Blade | n/a - Inertia is the view layer | diverged | Frontend |
| Inertia.js | First-class: v3 over Svelte 5 / React 19 / Vue 3.5 | shipped | Inertia Responses, Pages |
| Partial reloads | #[derive(Data)] + req.includes("subset") + Inertia's partial-reload protocol |
shipped | Type-safe include sets |
| Deferred props | Prop::deferred(...) + DeferConfig |
shipped | Inertia v3 deferred-props protocol |
| Merge props | MergeConfig + MergeStrategy::{Append, Prepend, Replace} |
shipped | Inertia v3 merge protocol |
| Encrypt history | EncryptHistoryMiddleware |
shipped | History encrypted at rest in the client |
| Scroll position | ScrollConfig + ScrollMetadata |
shipped | Auto-restore on navigation |
| TypeScript types | suprnova generate-types reads #[derive(InertiaProps)] and emits .d.ts |
shipped | TypeScript Types |
| Vite manifest reading | Auto-wired via Inertia::root_view |
shipped | HMR in dev, hashed assets in prod |
CLI
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
php artisan |
Per-app console binary built from #[command] macros |
shipped | Console, CLI overview |
make:controller / make:model / etc. |
suprnova make:controller / make:middleware / make:action / make:error / make:inertia / make:migration / make:task |
shipped | Generators |
serve |
suprnova serve (backend + Vite dev server together) |
shipped | Serve |
migrate family |
suprnova migrate / migrate:rollback / migrate:status / migrate:fresh |
shipped | Migrations CLI |
db:seed |
cargo run --bin console db:seed (via per-app console) |
shipped | Seeders registered via Seeder trait |
schedule:run / schedule:work / schedule:list |
Same names via per-app console binary | shipped | Scheduling CLI |
queue:work |
Same name via per-app console binary | shipped | Graceful shutdown on SIGTERM/SIGINT |
tinker |
No REPL | by design no | See the row in "Digging deeper" |
Deployment
| Laravel | Suprnova | Status | Notes / link |
|---|---|---|---|
php artisan optimize |
cargo build --release |
diverged | One binary, no opcache step |
php artisan config:cache |
Typed config is compile-time-checked already | diverged | No runtime cache to invalidate |
php artisan route:cache |
Routes are macro-expanded at compile time | diverged | The router is built at boot from already-typed routes |
| Envoy (SSH deploys) | Use any orchestrator - Docker, systemd, Kubernetes, fly.io, Railway | by design no | The binary is the deploy artifact |
| Forge / Vapor | Not ours to ship - but the recipes for Railway, DO, and Hetzner cover the same job | diverged | Deployment, Railway, Digital Ocean, Hetzner |
| Horizon (queue dashboard) | No dashboard yet | not yet | Failed-job inspection via cargo run --bin console queue:failed until then |
Packages (Laravel's official packages - ours either ship in core, ship as adapters, or are deliberate gaps)
| Laravel package | Suprnova | Status | Notes / link |
|---|---|---|---|
| Cashier (Stripe) | suprnova-payments-stripe |
shipped | Generic + adapter. Payments |
| Cashier (Paddle) | suprnova-payments-paddle |
shipped | MoR flow. Payments |
| Dusk | n/a | by design no | Cross-language browser tooling already exists (Playwright, etc.) |
| Envoy | n/a | by design no | Containers / systemd / orchestrators do the job |
| Fortify | Replaced by auth_flows |
shipped | Same job, integrated. Auth Flows |
| Folio | n/a - page-based routing isn't idiomatic Rust | by design no | Use routes! for explicit routing |
| Homestead | n/a - use Docker / DevContainers | by design no | Docker recipe |
| Horizon | n/a yet | not yet | Failed jobs surface via the per-app console |
| Mix | Replaced by Vite | diverged | Vite ships in every scaffold |
| Octane | n/a - we are already long-lived Tokio | by design no | Single binary, always warm, no FPM to swap out |
| Passport | n/a yet | not yet | Run a dedicated IdP behind Suprnova until shipped |
| Pennant (feature flags) | Re-implemented as features::* |
shipped | Feature Flags |
| Pint (PHP code style) | cargo fmt + cargo clippy |
diverged | Standard Rust toolchain |
| Precognition | Inertia precognitive requests via partial reloads + the same #[derive(Data, Validate, FormRequest)] types |
shipped | The two halves of Precog (early validation + lightweight reload) both fall out of Inertia v3 + form requests |
| Prompts (CLI UI) | Use the dialoguer / inquire crate when needed |
by design no | Rust ecosystem already covers this |
| Pulse | n/a yet | not yet | OTel today, dashboard later |
| Reverb (WebSocket server) | Built into Suprnova (ws!() + BroadcastHub) |
diverged | No separate server needed - it's the same process |
| Sail (Docker dev) | suprnova-cli ships Docker recipes inline |
shipped | CLI Docker |
| Sanctum | TokenGuard + bearer middleware |
diverged | Token model ships; no separate package surface |
| Scout (full-text search) | n/a yet | not yet | Vector search ships (Vector); keyword Scout-equivalent later |
| Socialite | Via the vendored torii fork | shipped | Authentication |
| Telescope | n/a yet | not yet | Tracing + OTel cover the diagnostic gap until a dashboard ships |
| Valet | n/a - Rust apps run directly | by design no | suprnova serve is the dev runner |
Macros (Rust-specific surface; closest Laravel analogues for context)
Suprnova ships a wide set of proc-macros that don't have a Laravel analogue because Laravel doesn't have macros - it has runtime reflection. Including them here so you don't miss them.
| Macro | Closest Laravel idea | What it does |
|---|---|---|
#[suprnova::model] |
extends Model |
Generates SeaORM entity + impls Model trait |
#[suprnova::observer(M)] |
User::observe(UserObserver::class) |
Registers an Observer<M> impl via inventory |
#[scopes(M)] |
Local scopes on a model | Adds methods to Builder<M> |
#[accessor] / #[mutator] |
Eloquent accessors / mutators | Field-level get/set hooks |
#[handler] |
Controller __invoke |
Auto-extracts typed params from Request |
#[command] / #[derive(Command)] |
Artisan command class | Registers a console subcommand |
#[policy] |
Policy class | Registers a Policy impl via inventory |
#[service(T)] |
Service provider register |
Binds T into the container |
#[injectable] |
Constructor injection | Generates an App::make-backed constructor |
#[derive(InertiaProps)] |
Inertia props | TypeScript codegen + Inertia serialization |
#[derive(Data)] |
Request DTO | Extractable from Request with include-set support |
#[derive(FormRequest)] |
FormRequest class |
Validation + auth gate + transformation |
#[derive(Factory)] |
Model factory | Faker-backed test data generation |
#[derive(Resource)] |
API Resource | JSON:API + Laravel-shape serialization |
#[workflow] / #[workflow_step] |
n/a in Laravel | Long-running stateful work |
routes! + get! / post! / ws! etc. |
Route::get / Route::post |
Compile-time route registration |
casts! |
protected $casts = [...] |
Per-model cast declaration |
attrs! |
Mass-assignment array | Partial-attribute builder |
json_response! / text_response! |
response()->json(...) |
Quick Ok(HttpResponse::...) |
See Macros for the full reference.
Helper functions (Laravel's global helpers; ours are typed)
Laravel ships hundreds of small globals (str_replace_first, array_flatten,
now(), tap(), optional() …). Most of them have a direct Rust equivalent
in std or a small standard crate, so Suprnova doesn't reintroduce them as a
single namespace. The ones that are useful to have aliased ship under their
home module.
| Laravel helper | Suprnova / Rust equivalent | Where |
|---|---|---|
auth() |
Auth::user().await? |
Authentication |
cache() |
Cache::get/put/... |
Cache |
config('app.name') |
Config::get::<AppConfig>()?.name |
Configuration |
csrf_token() |
csrf_token() (same name) |
CSRF |
dd() |
Builder::dd() (Eloquent query dump-and-die) / dbg!() from the stdlib |
Builder::dump() / Builder::dd() exist for query inspection; use dbg!() for general values |
env('APP_KEY') |
env("APP_KEY") / env_required("APP_KEY") / env_optional("APP_KEY") |
Configuration, Env Vars |
now() |
chrono::Utc::now() (re-exported as suprnova::chrono) |
- |
optional($x)->y |
x.as_ref().map(|x| x.y) |
Rust handles this with Option<T> directly |
redirect('/') |
redirect("/") (same name) |
Routing |
request() |
Request is passed into your handler |
Requests |
response() |
HttpResponse::json/text/redirect/... |
Responses |
route('posts.show', ['post' => 1]) |
url("posts.show", &[("post", "1")]) |
URL Generation |
session('key') |
session().get("key") |
Session |
str() / Str::camel($x) |
heck crate methods (ToUpperCamelCase, etc.) |
- |
tap($x, fn) → $x |
tap from tap crate, or dbg! for quick inspection |
Use the tap crate idiomatically |
today() |
chrono::Utc::now().date_naive() |
- |
value($x) |
Just call the closure: x() |
n/a - Rust closures need no helper |
view('home', $data) |
Inertia response: Inertia::render("Home", data) |
Inertia Responses |
What we genuinely don't have yet
A consolidated list of every not yet above, so you can see the shape of the gap in one place:
| Area | What's missing | Workaround until shipped |
|---|---|---|
| Search (Scout - keyword) | Algolia / Meilisearch / Elastic adapter | Roll your own with meilisearch-sdk / elasticsearch until shipped; Vector handles semantic search today |
| Passport (OAuth server) | First-party OAuth identity provider | Run Hydra / Keycloak behind Suprnova |
| Telescope (debug dashboard) | Web UI for requests / queries / events / cache hits | Use OTel + tracing output (Observability) |
| Pulse (perf dashboard) | Web UI for slow queries / errors / hot routes | Same: OTel surface today, dashboard later |
| Horizon (queue dashboard) | Web UI for queue depth / failed jobs / throughput | cargo run --bin console queue:failed and OTel metrics |
What we won't ship (and why)
| Laravel feature | Why Suprnova doesn't have it |
|---|---|
| Tinker (REPL) | Rust doesn't have a productive REPL story for compiled binaries. A short #[suprnova_test] or a one-off cargo run --bin <thing> script does the job |
| Blade templates | Inertia is the view layer; we don't ship a parallel server-rendered template engine |
helpers.md kitchen-sink |
Rust ships std + small focused crates (heck, chrono, regex); we don't reintroduce a single global namespace |
| Mix | Vite covers it and ships in every scaffold |
| Octane | Suprnova is already long-lived Tokio; there's no FPM mode to optimise out of |
| Dusk (browser tests) | Cross-language tooling (Playwright, WebdriverIO, gstack agent browser) already solves this |
| Sail (Docker dev) | Docker recipes ship inline (CLI Docker); no separate package needed |
| Valet | suprnova serve is the dev server |
| Envoy (SSH deploys) | Containers / systemd / orchestrators do the job; we don't need a bespoke SSH DSL |
Concurrency facade (Concurrency::run) |
Tokio (tokio::join! / tokio::spawn / tokio::select!) is the answer; no facade needed |
| Processes facade | tokio::process::Command is already the right shape |
| First-party AI SDK / MCP / Boost | Pick the Rust crates you already use; we don't gatekeep |
| Dedicated Redis facade | Cache/queue/rate-limit cover 95% of typical use; reach for the redis crate when you need ad-hoc commands |
| Strings facade | heck, regex, std::str cover it; no Str::camel($x) global |
| Prompts (CLI UI library) | dialoguer / inquire already exist; we don't reinvent |
| Laravel-style PHP/JSON translation files | Localization ships, but the catalog format is Fluent .ftl - one format the server and the browser both parse. trans_choice has no equivalent either: Fluent selects CLDR plural categories inside the message. Localization |
How this list stays honest
Every row in the shipped column is verifiable by:
- Grepping
framework/src/lib.rsfor the named export - Running the framework test suite (
cargo test --workspace) - Reading the linked chapter
Every row in the not yet column is intended work, not a refusal. Every row in the by design no column has a one-sentence reason in the Notes column; those reasons are the design principles in Introduction applied to a specific feature.
If you find a Laravel feature you reach for that isn't on this map, open an issue - it either has a Suprnova answer that's missing a row, or it's a real gap and we want to know.
Next
- From Laravel - the same map, narrated as a side-by-side
- Introduction - the design principles this parity work follows
documentation.md- the master TOC across every chapter
