Manual contentsReferenceBrowse 106 chapters
Manual 32 min read

Laravel Parity Map

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 ApplicationServerhandle_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
Contextual binding (when()->needs()->give()) No contextual bindings - one binding per trait per container layer by design no The container is TypeId-keyed with no runtime reflection to key a binding on "who is asking". Compose explicitly: pass the dependency in, or bind a distinct newtype per consumer. 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 Per-session token validation is the default. Optional SameOriginOnly, AllowSameSite, and OriginOnly policies consult Sec-Fetch-Site; origin enforcement is not enabled by default. 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_response!() / text_response!() / Redirect::to / Inertia responses shipped Responses
Streamed responses (eventStream, stream, streamJson) HttpResponse::sse(...) / event_stream(...) / stream_bytes(...) / stream_json(...) shipped Same wire shapes @laravel/stream-{react,vue,svelte}'s hooks expect. SSE
withoutCookie / withoutCookies .without_cookie(name) / .without_cookies([...]) on HttpResponse, Response, Redirect, RedirectRouteBuilder shipped Cookie::forget_with(name, path, domain) for a cookie that wasn't set at /
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 Database-backed by default via DatabaseSessionDriver; the encrypted browser cookie carries the session identifier and activity-touch metadata, not the session data bag. Session
Cookie queue (Cookie::queue) Cookie::queue/queued/unqueue/expire - a task-local jar SessionMiddleware drains onto the response shipped Requires SessionMiddleware in the chain; queued by name, not name+path like Laravel's CookieJar
Validation #[derive(Validate)] + 35 built-in rules + Rule/ValueRule/AsyncRule traits shipped Url uses Laravel's scheme allowlist and Url::protocols([...]) mirrors url:http,https. Async rules (e.g. Unique) hit the DB. ArrayKeys/Distinct are ValueRules over serde_json::Value, matching Laravel's array:keys and distinct. InArray takes the other field's list directly instead of Laravel's in_array:other.* rule string, and Contains/DoesntContain match JSON string elements exactly. Gt/Gte/Lt/Lte take an explicit CompareWith operand (literal number, numeric sibling, or sibling compared by character count) instead of inferring Laravel's four size measures; array and file comparisons have no counterpart. Validation
Password rule (Password::defaults(), uncompromised()) Password::min(n) + strength builders (.letters(), .mixed_case(), .numbers(), .symbols()) + .uncompromised() shipped Have I Been Pwned k-anonymity check; fails open on a network error, matching Laravel's NotPwnedVerifier. 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
Log channels / file drivers (single, daily, monthly, stack) tracing writes structured lines to stdout; the platform rotates and ships them by design no Containers, systemd, and every log shipper already do rotation and retention. Re-implementing it in-process duplicates the platform and hides logs from it. 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. On a local disk, put/write, the streaming writer, and copy are all staged under <root>/.suprnova-atomic/ and published in one step, and a conditional write is published with link(2) so it stays a true exclusive create; append is the one in-place operation. Laravel's local driver writes straight to the target, where a partial length is observable and a crash truncates the live object. Storage::register_read_through composes two disks into a read-through disk that promotes fallback hits onto the primary, with copy: false to skip promotion and fallback-spanning copy / rename. 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; .retry_when(predicate) narrows the built-in retry policy with a RetryContext. HTTP Client
Image (Illuminate\Image) Image::from_bytes/from_path/from_disk/from_upload/from_stream + the same operation and terminal surface shipped Lives in suprnova::media. Two drivers like Laravel's gd/imagick: IMAGE_DRIVER=oxideav (default, pure Rust) or magick. Reads and writes PNG, JPEG, WebP, GIF, BMP; AVIF output is deferred pending the in-house AV1 encoder publish. Header-checked decode limits. Images
HEIC decoding in the default driver IMAGE_DRIVER=magick on a host with the libheif delegate by design no HEVC is patent-encumbered and the only credible pure-Rust decoder is dual AGPL/commercial, so no built-in decoder ships. Same shape as Laravel, where GD cannot read HEIC at all and Imagick needs the delegate compiled into both the binary and the PHP extension. Images
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 Mail::to(...).send(MyMail { ... }).await? + drivers smtp/ses/mailgun/postmark/sendgrid/resend/log/memory/file shipped Mailable trait + Tera-rendered HTML/text bodies; SES sends carry TenantName / ConfigurationSetName / ListManagementOptions; queued dispatch routes via .on_queue(...) / .on_connection(...), outranking Queue::route. Mail
Notifications Notify::send(&user, notif).await? + channels mail/database/broadcast/webpush shipped Notifiable trait + Notification per channel; queued dispatch (Notify::queue) carries per-notification queue/timeout/fail_on_timeout/max_tries/backoff onto each channel's job via the same EnvelopeOverrides primitive Mail uses. 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
Job-declared delay fn delay() -> Option<Duration> on Job, honored by Queue::push and Queue::bulk shipped An explicit Queue::push_later / Queue::later(delay, job) call always wins over the job's own default. Queues
Queue::forward Queue::forward(from, to) / Queue::forward_on(from, to, connection), applied to the envelope and to the worker's --queue list shipped Queue-to-queue only: connection gates the redirect rather than selecting a driver, and is compared against the process connection name so the push and the worker's claim gate on the same value; to is required where Laravel's is optional. Queues
Unique-job skipped event queue::events::UniqueJobSkipped { job_name, unique_id, connection } shipped Fired on the push side when push_unique dedupes; the call still returns Ok(false)
Queue pausing (queue:pause / queue:resume) Queue::pause/resume/pause_all/resume_all/is_paused/paused_queues, cache-backed, with QueuePaused / QueueResumed / QueuesPaused / QueuesResumed events shipped A per-queue pause only takes effect on a worker started with an explicit --queue=... list; resume_all doesn't clear a per-queue pause. A running worker also emits WorkerQueuePaused / WorkerQueueResumed once per transition and queue:work prints a line for each; their queue field is Option<String>, because a worker started without --queue has no queue names to report under a global pause. Queues
Retrying transient Redis commands Read-shaped Redis commands retry once on a connection-level failure, each attempt awaiting the driver's reconnect budget; REDIS_COMMAND_RETRIES adds more shipped Laravel's command_retries covers every command through one dispatch point and a 60-entry allowlist; Suprnova retries per call site, and no setting makes a write or a queue pop retry. Cache
After-commit dispatch (afterCommit()) fn after_commit() -> bool on Job, EnvelopeOverrides::after_commit per push, Queue::push_after_commit shipped The whole push waits for the commit, events included, and a rollback discards it; a deferred push_unique still takes its lock immediately so dedupe works inside the transaction. Manual DB::begin_transaction never defers. Queues
Failover queue connection FailoverQueueDriver over an ordered connection list, via QUEUE_DRIVER=failover + QUEUE_FAILOVER_CONNECTIONS shipped Writes fall through the list; pop, the counters and the listings stay on the first connection, so each fallback needs its own worker. QueueFailedOver is edge-triggered, and bulk_push falls through per envelope so each keeps its own delay. Queues
ShouldBeUniqueUntilProcessing fn unique_until_processing() -> bool on Job, released after the middleware pass and before the handler shipped Owner-scoped release, so a redelivered attempt never releases a newer dispatch's lock. A job a middleware releases back onto the queue keeps its lock. Queues
Debounced jobs (#[DebounceFor]) Job::debounce_for / max_debounce_wait / debounce_id, plus Queue::push_debounced(job, DebounceOptions) shipped Trait methods rather than a class attribute, so a typo is a compile error. Every dispatch is enqueued and the collapse is settled at the worker; a job declaring both debounce_for and unique_id is refused with a FrameworkError where Laravel throws. Chains and batches refuse a debounced job outright. Queues
Debounced queued listeners Job::debounce_for on the listener's job, or DebouncedListener::new(window, build).keyed_by(...) shipped Laravel puts the attribute on the listener class; Suprnova's listener-to-job bridge already runs through Queue::push, so declaring it on the job covers the common case and DebouncedListener covers a per-registration window. Events
Queue inspection (pendingJobs / delayedJobs / reservedJobs) Queue::pending_jobs(queue) / delayed_jobs / reserved_jobs, Option<&str> collapsing Laravel's all*Jobs() twin into one call shipped InspectedJob DTO (id/queue/name/attempts/payload/created_at); the trait default is an honest Err rather than an empty collection; sync/null override with Ok(vec![]); Redis's reserved_jobs is per-consumer. Unlike Laravel these do not follow a Queue::forward, so they report the literal queue you name - which is how a backlog left behind on a forwarded queue stays visible. Queues
Schedule per-task timezone .timezone(chrono_tz::Tz) / .try_timezone("name") per task, Schedule::timezone default, schedule:list --timezone shipped Typed chrono_tz::Tz instead of Laravel's string; the schedule-wide default is Schedule::timezone in schedule::register rather than an app.schedule_timezone config key, and an unpinned task keeps the process-local zone. Scheduling
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
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 shipped Provider-backed and actor-bound - Auth flows
Password Reset PasswordReset + Magnetar first-email-proof transaction or verified UserProvider fallback + reset/change mail shipped Magnetar handles atomic first proof; provider-backed apps can reset already verified users - Auth flows
Brute-force throttling Magnetar lockout engine + BruteForce + LoginThrottleMiddleware shipped Account lockout plus framework IP/route limiting
Two-Factor (TOTP) Framework TwoFactor compatibility facade plus Magnetar factor engine shipped Recovery codes, replay protection, and factor-gated integrated sign-in
Remember-me Magnetar purpose-bound rotating credential behind the framework cookie shipped Auth-epoch checks, rotation, anomaly handling, and legacy fallback
OAuth (Socialite) Magnetar provider registry and Auth::oauth(provider) facade shipped OAuth, Apple form_post, PKCE/state binding, verified identity policy - OAuth
Sanctum (API tokens) BearerTokenMiddleware over Magnetar bearer sessions diverged Authenticates bearer sessions; no separate Sanctum token-management API
Passport (OAuth server) Magnetar protocol and plugin engines diverged Engine primitives ship; no Laravel Passport-compatible application facade
Fortify (auth backend) Framework Auth/auth_flows facades over Magnetar engines shipped Framework owns HTTP, mail, events, cookies, and application binding
Authorization (Policies / Gates) Gate::allows/denies + #[policy] impl PostPolicy + Authorizable trait + macro registration + Gate::default_denial_response 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()
Postgres keepalives_* DSN options DB_IDLE_TIMEOUT / DB_MAX_LIFETIME / DB_ACQUIRE_TIMEOUT / DB_TEST_BEFORE_ACQUIRE / DB_PING_AFTER_IDLE pool liveness diverged sqlx exposes no TCP keepalive setter, so Suprnova recycles and pings pooled connections instead. Database
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
whereBinary() family Builder::where_binary / or_where_binary / where_not_binary / or_where_not_binary, and DB::table(...).where_binary(...) shipped MySQL and MariaDB emit = binary; Postgres and SQLite return an error instead of a collation-dependent match. 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
wherePivot family (incl. the closure form) where_pivot / where_pivot_op / where_pivot_in / where_pivot_not_in / where_pivot_null / where_pivot_not_null / where_pivot_between / where_pivot_not_between / where_pivot_group plus or_ twins diverged Reads only - a pivot filter never narrows attach / detach / sync, and eager loads do not carry it. 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 One UPDATE per BelongsTo owner, one level deep and event-free (no grandparent recursion, no parent saved event). without_touching / without_touching_on::<M, _, _>() to skip. Parent touching
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
modelKeys() Builder::model_keys().await? (no hydration, qualified key) and Collection::model_keys() shipped Both return Vec<M::Key>; the builder terminal projects users.id so it survives joins
API Resources #[derive(Resource)] + IntoJsonResource + JsonApiResponse + fieldsets + includes shipped JSON:API shape + Laravel-style resource shape both available. ?include= paths are capped at max_relationship_depth (default 5), matching JsonApiResource::$maxRelationshipDepth. 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
refreshForUpdate() model.refresh_for_update().await? shipped SELECT ... FOR UPDATE reload; no-op lock on SQLite. Row locking
inOrderOf(col, values) Builder::in_order_of(col, values) shipped Bound CASE WHEN ordering; unlisted values sort last. Typed builder only. Ordering
orWhereKey / orWhereKeyNot Builder::or_where_key(id) / Builder::or_where_key_not(id) shipped Fold into the preceding clause as a disjunction; or_filter_key / or_filter_key_not aliases
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 the same process, normally through a loopback hyper connection so the server receives a real Incoming body shipped HTTP Tests
TestResponse wrapper suprnova::testing::TestResponse - fluent assert_status / assert_json_path / assert_cookie / assert_session_has and friends, all chaining &Self shipped HTTP Tests
Inertia testing helpers suprnova::testing::AssertableInertia - component/url/version/prop/has/missing/where_/count/has_flash, plus reload_only/reload_except/load_deferred_props via a caller-supplied with_reload closure 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>() shipped Creates a fresh per-test in-memory SQLite database, applies migrations, registers it in the test container, and discards that isolated database/container state on drop; it does not wrap each test in a rollback transaction. Database Tests
Mocking & fakes Per-facade fakes: MailFake, NotifyFakeGuard, EventFakeGuard, Queue::fake, Bus::fake, Http::fake, Storage::fake shipped Recorded calls + assertion helpers. Mocking
QueueFake job uuids queue::testing::pushed_with_id::<J>() shipped The fake stamps an envelope id per push and emits the same JobQueued a real push does
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
Route::inertia($uri, $component, $props) Router::inertia(path, component, props) shipped Returns a RouteBuilder, so .name(...) / .middleware(...) chain; Router::view is the older alias
Page URL resolution (Inertia::resolveUrlUsing) page.url is path + query; override with InertiaConfig::url_resolver shipped Default derivation matches the version middleware's X-Inertia-Location byte for byte; a url_resolver changes page.url only
Inertia protocol middleware (Vary, empty response, version bounce) InertiaHeadersMiddleware + InertiaVersionMiddleware + Inertia303Middleware - three of the middlewares Inertia::install wires (validation-error redirect and error pages are the next two rows) shipped Vary: X-Inertia on every response; empty 200 on an Inertia visit becomes 303 back; the 409 bounce re-flashes the session
Validation-error redirect (Middleware::resolveValidationErrors, $withAllErrors) InertiaValidationRedirectMiddleware, wired by Inertia::install; InertiaConfig::with_all_errors(bool) shipped A 422 on an Inertia visit becomes 303 back with the errors flashed; a field's value collapses to its first message unless with_all_errors(true). Inertia Responses
Error pages (Inertia::render('Error', ['status' => …]) from the exception handler) InertiaConfig::error_page("Error"); InertiaErrorPageMiddleware, wired by Inertia::install when a component is named shipped One config line instead of an exception-handler edit. A 403 / 404 / 429 / 500 on an Inertia visit or a browser navigation renders the named page at the original status, with status / message / request_id props; API clients, 422s, X-Inertia-Location bounces, and any body that is not an error envelope are untouched. The three starters ship the page and enable it. Inertia Responses
External redirect + history clearing InertiaResponse::location_for(&req, url), App::clear_history() shipped location_for is 409 for XHR and 302 for a hard navigation; App::clear_history() survives the logout redirect
Inertia::share / getShared / flushShared App::inertia_share / _lazy / _once, App::inertia_shared(key), App::flush_inertia_shared() shipped Dot-key nesting via Arr::set semantics; per-request InertiaSharedData::share(&req, component) can vary by page. A dotted share stays flat until the response's unpacking pass, so only/except match an ancestor entry (only: ['auth'] reaches auth.user) where Laravel gets the same result from Arr::set at share time
Partial reloads #[derive(Data)] + req.includes("subset") + Inertia's partial-reload protocol shipped Type-safe include sets. ?include= gates every lazy flavor including lazy(deferred), and runs ahead of X-Inertia-Partial-Data so a disallowed include still returns 400. errors is exempt from only/except, matching Laravel's Inertia::always share
Deferred props .defer(…) / .defer_with(…, DeferOptions), or Prop::…defer() shipped Inertia v3 deferred-props protocol; DeferOptions carries the group and the rescue flag. deferredProps ships on the initial visit only - resolveDeferredProps returns [] on any matched partial
Merge props .merge / .merge_prepend / .deep_merge / .merge_with(MergeStrategy) / .merge_lazy / .merge_lazy_with, or Prop::…merge().merge_with_path(...) shipped Inertia v3 merge protocol; match_on takes one field or several; merge_with_path merges a nested field instead of the prop's root
Prop composition (defer()->merge(), merge()->once(), optional()->once()) Prop flag builder + InertiaResponse::prop(key, prop) shipped Prop is a struct of orthogonal flags, mirroring the PHP adapter's Deferrable / Mergeable / Onceable interfaces
Encrypt history EncryptHistoryMiddleware shipped History encrypted at rest in the client
Scroll position .scroll / .scroll_with / .scroll_wrapped / .paginate + ScrollMetadata / ProvidesScrollMetadata shipped Auto-restore on navigation; reset reads X-Inertia-Reset, matching resolveScrollProps
TypeScript types suprnova generate-types reads #[derive(InertiaProps)] and emits .d.ts shipped TypeScript Types
Vite manifest reading Auto-wired via InertiaConfig::manifest_path shipped HMR in dev, hashed assets in prod. Inertia::install fails closed in production when the manifest is missing
Asset version from the build manifest InertiaConfig default: VersionResolver::from_manifest(manifest_path) shipped Hash of the manifest bytes; static "1.0" fallback when there is no build to hash
Inertia SSR (inertia:start-ssr) InertiaConfig::ssr(...) on the config passed to Inertia::install, worker launched by suprnova ssr:start shipped Out-of-process worker over HTTP loopback; falls back to CSR on error or timeout unless ssr_throw_on_error(true). InertiaConfig::ssr_bundle_path(...) gates dispatch on the built bundle existing on disk (mirrors ensure_bundle_exists), toggled with .ssr_ensure_bundle_exists(bool) (on by default once a bundle path is set); suprnova new scaffolds frontend/src/ssr.{ts,tsx} and a build:ssr script for every starter; suprnova ssr:check verifies the worker's GET /health route. Inertia Responses

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. Skips the Vite pane on a --api project instead of refusing to start.
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; a targeted run prints RUNNING / DONE with elapsed milliseconds
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
Maintenance mode (php artisan down / up) ./app down / ./app up - bypass secret with a server-checked 12-hour expiry, custom retry/message/except paths, file or cache driver shipped Deployment
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 BearerTokenMiddleware over Magnetar bearer sessions diverged No separate package or personal-access-token management surface
Scout (full-text search) n/a yet not yet Vector search ships (Vector); keyword Scout-equivalent later
Socialite Magnetar provider registry and Auth::oauth(provider) shipped OAuth
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
php artisan dev --tabs (TUI multi-pane dev-process mode) Single-terminal, [name]-prefixed output is the Rust dev-tooling norm (cargo watch, bacon, just) - suprnova serve already gives every process (backend, frontend, and any Suprnova.toml entry) its own colored prefix and auto-restart. A tabbed TUI is a second interaction model for a signal this already provides; --stream's job - one scriptable, real-time output stream - ships as suprnova serve --json (NDJSON, one event per line). Serve

How this list stays honest

Every row in the shipped column is verifiable by:

  1. Grepping framework/src/lib.rs for the named export
  2. Running the framework test suite (cargo test --workspace)
  3. 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.

Last reviewed against Laravel 13.25.0.

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