Short term (0-3 months)
The short-term horizon is mostly debt and gaps in existing packages rather than new things. The goal is to take everything currently labelled experimental to beta and unblock common app shapes that today require user-supplied glue.
Recently shipped
- Rate limiting -
@miiajs/rate-limitshipped in 0.2 (beta): fixed-window limiter with aRateLimitStorecontract whoseincrement()counts the hit and decides blocking atomically; the guard flow with@RateLimit/@SkipRateLimit(replacement semantics,@BodyLimit-style precedence) plus a perimeterrateLimit()middleware; draft-6RateLimit-*headers;ctx.ip/trustProxylanded in core to support it. Deliberate departures from the original plan below: one package instead of three, fixed window instead of token bucket,increment()instead ofconsume()- a simpler default, with the store contract already shaped for Redis. - Request body size limits - shipped in 0.2.0:
maxBodySizeapp option +@BodyLimitper-route overrides, enforced in core and in the adapters. See Body size limits.
Validating the experimental packages
Two packages currently sit at experimental because they have not been validated by real adoption, not because their internal coverage is poor.
| Package | Status | Why experimental | What promotes it |
|---|---|---|---|
@miiajs/cli | experimental | 51 tests, but the 11 scaffold features have not been exercised by real miia new runs in production projects. | Run miia new to bootstrap a real app, fix anything that breaks, write end-to-end scaffold tests. |
@miiajs/messaging-redis | experimental | 30 tests; 1 still TODO (survives consumer crash via XAUTOCLAIM, marked it.todo). No production deployment. | Build a small worker app on top of it, exercise crash recovery, write the XAUTOCLAIM test. |
@miiajs/auth/oauth2 recipe page is also marked experimental until the GitHub OAuth flow is validated end-to-end on a real app. The plan is to keep OAuth2 as a recipe rather than a separate package - see Examples and docs gaps.
Test depth on small-surface packages
These are beta (small but well-targeted test coverage) but would benefit from a few more edge-case tests as we hit them in real apps. None block their beta status today.
| Package | Tests today | Edges worth adding when we touch them |
|---|---|---|
@miiajs/jwt | 6 | Algorithm allowlist enforcement, expired tokens, key rotation, missing config. |
@miiajs/auth | 12 | Multi-strategy resolution, token extractor edge cases, AuthGuard composition. |
Multipart / file upload (@miiajs/multipart)
plannedawait ctx.req.formData() already works today via Web Standards - good enough for small uploads where loading the whole body into memory is fine. The gap is streaming large files and a uniform UploadedFile shape across runtimes.
The package will be a thin per-runtime layer behind a single decorator. Each runtime gets the fastest streaming path it natively supports:
- Bun / Deno - native streaming
formData(). Bun 1.1+ and Deno 1.40+ both expose multipart parts asReadableStream, so the parser is the runtime's job. - Node.js / uWebSockets -
busboy(the battle-tested stream parser that powersmulterand@fastify/multipartunder the hood).
Method-level decorator (similar to @ValidateBody) wraps a Koa-style middleware - no new framework concept, just a more ergonomic surface than raw @Use. Built-in: file size limits, MIME filters, abort handling, direct pipe to your storage of choice (S3 SDK, GCS client, local filesystem). We do not reimplement the multipart protocol on Node.js; busboy does the parsing. Bun/Deno parsing stays delegated to the runtime. The exact API surface gets locked in once we exercise it on a real upload-heavy app.
Why now: every real app eventually needs file upload, and "use formData() for small files, write your own busboy plumbing for large ones" is not a great pitch for a "decorators + Web Standards" framework.
Rate limiting stores (@miiajs/rate-limit-redis + @miiajs/rate-limit-upstash)
plannedThe core package shipped in 0.2 with an in-memory store. What remains is the distributed story - two store packages built against the existing RateLimitStore interface:
@miiajs/rate-limit-redis- ioredis-backed store using a single Lua script for the atomic increment + window expiry + block decision. Covers typical Node/Bun/Deno deployments.@miiajs/rate-limit-upstash- thin adapter over@upstash/redis(HTTP REST API). This is the edge story: works on Vercel Edge, Netlify, Cloudflare Workers, and any other runtime where TCP-based clients like ioredis don't start.
Sliding window and token bucket algorithms are candidate extensions of the RateLimitStore contract, most likely landing together with the Redis store (fixed window shipped first as the simpler default).
Why our own. Wrappers over rate-limiter-flexible looked appealing but bring two problems: official Bun support is unverified, and the library's store list is Node-shaped (no native edge story). The RateLimitStore interface is ~20 lines and we control runtime compatibility by construction. Same pattern as IdempotencyStore in messaging - users learn one shape across the framework.
Serverless story. Unlike cache, rate limiting needs atomic counter increments with TTL. Unstorage's getItem/setItem is not atomic - we cannot reuse that bridge here without race conditions under load. Instead, the dedicated @miiajs/rate-limit-upstash package gives Vercel/Netlify/CF Workers users a working store from day one.
Because RateLimitStore is our interface, native first-party drivers (@miiajs/rate-limit-cloudflare-do for Durable Objects, @miiajs/rate-limit-dynamodb for AWS Lambda) can land later without breaking users. They swap the store instance, nothing else changes.
Server-Sent Events helper
plannedctx.res.sse(stream | iterable) helper plus a small SseStream builder that handles event framing, keep-alive comments, and back-pressure. No decorator, no new package - this lives in @miiajs/core and composes with existing guards/middleware/DI. There will not be a @miiajs/sse package.
@Get('/events')
async stream(ctx: RequestContext) {
return ctx.res.sse(async function* () {
yield { event: 'tick', data: { now: Date.now() } }
})
}
Why now: SSE solves 70% of "I need server-push" use cases (notifications, progress updates, log tails) without WebSocket-level complexity. WebSocket lands mid-term; SSE is ~50 lines of code today and unblocks real apps now.
Request validation pipe sugar
idea@ValidateBody/@ValidateQuery/@ValidateParams exist but are Zod-flavoured. Add a thin pipe layer so consumers can plug arbitrary schema libraries (Valibot, ArkType, Yup) by passing a safeParse-shaped function. Mostly already there via ZodLike interface; needs docs and one or two non-Zod tests to demonstrate.
Examples and docs gaps
Add small, focused example apps and recipes so contributors and users can copy-paste:
examples/multipart-upload- file upload with progress, alongside the new package.- Auth recipes: validate the existing OAuth2 recipe end-to-end with GitHub/Google, add API-key recipe, add session-based recipe. Recipes only - no separate auth packages (per design choice).
- Production logger recipes: how to plug winston / pino into the existing
LoggerServiceinterface for structured JSON logs. The interface is already there; the docs aren't. - Health check recipe: small core helper plus a controller pattern. We deliberately do not ship a
@miiajs/healthpackage - 50 lines of glue do not earn one. - Transactions recipes: explicit
db.transaction()patterns for Drizzle, Mongoose, Papr. No@Transactionaldecorator, noAsyncLocalStorageindirection.
Out of scope (deferred to mid-term)
- WebSocket support
- OpenTelemetry integration
- Cache abstraction
These are valuable but bigger commitments than 0-3 months. Items removed from short-term entirely after re-scoping: dedicated health-checks package (now a recipe), separate auth-strategy packages (recipes only, never packages), Request-Reply for messaging (long-term), scheduler (long-term idea).