@avelon/next
@avelon/next is the v1 adapter. It turns a RouteManifest into Next's file router, serves writes as generated server actions, wraps pages around the core kernel, and bridges middleware.ts. Reach for it when the application is a Next app and you want typed routes/web.ts without putting Next types in @avelon/core.
The adapter is a translation layer at three seams: mount, toRequest, and toResponse. Middleware, binding, validation, and exception mapping stay in the kernel (D26).
Installation
bun add @avelon/next
Wire the adapter in avelon.config.ts and generate the router during dev and build.
import { NextAdapter } from '@avelon/next'
import { createKernel, defineConfig } from '@avelon/core'
const adapter = new NextAdapter({ root: process.cwd() })
defineConfig({
name: 'app',
adapter,
drivers: {},
})
await adapter.mount(Route.manifest(), { kernel: createKernel() })
Basic Usage
import { Route } from '@avelon/next'
import { PostController } from '@/app/Http/Controllers/PostController'
import { Post } from '@/app/Models/Post'
Route.get('/', HomeController, 'index').name('home')
Route.middleware('auth').group(() => {
Route.resource('posts', PostController).bind('post', Post)
})
reeve route:sync (and NextAdapter.mount) wipe app/(web) and rewrite it. Generated pages are four lines and contain no logic:
// Generated by `reeve route:sync`. Do not edit.
import { PostController } from '@/app/Http/Controllers/PostController'
import { page } from '@avelon/next'
export const dynamic = 'force-dynamic'
export default page(PostController, 'show', '/posts/{post}')
Writes become server actions in framework/routing/actions.generated.ts. .api() additionally generates app/(api)/api/.../route.ts so JSON URLs do not collide with pages.
Capabilities
| Capability | Value | Meaning |
|---|---|---|
fileSystemRouting | true | mount() writes framework-owned files. |
serverActions | true | Route.post() is served as a generated server action. |
streaming | true | The kernel may return StreamResult. |
edgeMiddleware | false | Next 16 Proxy middleware defaults to Node (D27). |
serverActions selects the write transport. It does not gate whether a write route exists.
Request Lifecycle
page() and dispatch() convert native Next inputs with toRequest, call kernel.dispatch, then toResponse. Redirects throw NextRedirect with a NEXT_REDIRECT digest so Next navigation keeps propagating. Validation failures return { kind: 'action', ok: false, errors } for useActionState.
middleware.ts calls middleware(request). Routes that declare auth redirect to /login when no cookies or Authorization header are present; everything else returns { kind: 'next' }.
Method Reference
| Method / export | Signature | Description | |
|---|---|---|---|
NextAdapter | class NextAdapter | File-routing adapter implementing the frozen Adapter contract. | |
NextAdapter.mount | (manifest, options) => Promise<MountResult> | Writes the router and binds the kernel. | |
NextAdapter.attach | (manifest, options) => void | Binds kernel and manifest without rewriting generated files. | |
NextAdapter.registerViews | (views) => void | Maps string view tokens to React components for toResponse. | |
NextAdapter.toRequest | (native) => Promise<HttpRequest> | Converts page props, FormData, or Request. Unparseable bodies become body: null. | |
NextAdapter.toResponse | (result) => Promise<NextNativeResponse> | Converts kernel results. Redirects throw NextRedirect. | |
nextCapabilities | typeof nextCapabilities | Literal capability object used by CLI and codegen. | |
bindAdapter | `(adapter \ | undefined) => void` | Records the adapter generated helpers dispatch through. |
getAdapter | () => NextAdapter | Returns the bound adapter or throws. | |
setRuntimeBoot | `(boot \ | undefined) => void` | Registers a boot function page and dispatch call when unbound. |
ensureAdapter | () => Promise<NextAdapter> | Returns the bound adapter, booting first when needed. | |
page | (controller, action, uri) => PageComponent | Next page wrapper used by generated page.tsx. | |
dispatch | (controller, action, uri, formData) => Promise<NextNativeResponse> | Server-action entry used by generated writes. | |
handleRoute | (controller, action, uri, request, params?) => Promise<NextNativeResponse> | JSON route-handler entry used by .api() files. | |
middleware | (request, routes?) => Promise<NextNativeResponse> | proxy.ts / middleware.ts bridge. Pass generated routes on the Node proxy. | |
MiddlewareRoute | interface | Method, path, and middleware aliases for Edge auth. | |
setCookie | (name, value, options?) => void | Queues a cookie write for the current Next request. | |
clearCookie | (name, options?) => void | Queues a cookie deletion for the current Next request. | |
PendingCookie | interface | Queued cookie name, value, and options. | |
ViewRegistry | type | String view tokens mapped to render functions. | |
formProps | (name, params?) => { action, method, fields } | Hidden fields and action export for a named write route. | |
generateRouter | (manifest, root, options) => Promise<readonly string[]> | Destructive codegen used by mount. | |
findMountedRoute | (controller, action, uri) => RouteDefinition | Resolves a generated helper back to its manifest entry. | |
Route.get | (path, controller, action?) => RouteBuilder | Registers a GET route. | |
Route.post | (path, controller, action?) => RouteBuilder | Registers a POST route. | |
Route.put | (path, controller, action?) => RouteBuilder | Registers a PUT route. | |
Route.patch | (path, controller, action?) => RouteBuilder | Registers a PATCH route. | |
Route.delete | (path, controller, action?) => RouteBuilder | Registers a DELETE route. | |
Route.resource | (name, controller) => ResourceBuilder | Expands the seven resource routes. | |
Route.middleware | (...names) => { group } | Applies middleware inside a group. | |
Route.prefix | (prefix) => { group } | Prefixes routes inside a group. | |
Route.all | () => readonly RouteDefinition[] | Returns registrations. | |
Route.manifest | (version?) => RouteManifest | Builds a mountable manifest. | |
Route.find | (name) => RouteDefinition | Finds a named route or throws. | |
Route.reset | () => void | Clears registrations. | |
RouteBuilder.name | (name) => this | Sets the stable route name. | |
RouteBuilder.middleware | (...names) => this | Appends middleware aliases. | |
RouteBuilder.bind | (param, model) => this | Marks a path parameter for binding. | |
RouteBuilder.api | () => this | Additionally generates a JSON route handler. | |
ResourceBuilder.bind | (param, model) => this | Binds member parameters. | |
ResourceBuilder.only | (...actions) => this | Keeps named resource actions. | |
ResourceBuilder.except | (...actions) => this | Drops named resource actions. | |
ResourceBuilder.api | () => this | Marks remaining resource routes as .api(). | |
route | (name, params?) => string | Fills {param} placeholders. | |
exportName | (definition) => string | Stable generated export for a write route. | |
uriToSegments | (uri) => string | Converts /posts/{post} to posts/[post]. | |
nativeToRequest | (native) => Promise<HttpRequest> | Low-level native conversion. | |
kernelToResponse | (result) => Promise<NextNativeResponse> | Low-level result conversion. | |
NextRedirect | class NextRedirect extends Error | Control-flow throw with a NEXT_REDIRECT digest. | |
isNextControlFlow | (error: unknown) => boolean | True for Next navigation throws. | |
PageProps | interface | Async Next page props. | |
NextNativeRequest | type | page, form, or http native inputs. | |
NextNativeResponse | type | View, action, redirect, stream, or next. | |
NextAdapterOptions | interface | root plus optional capability overrides. |
Testing
Mount the adapter against a temp directory and call page / dispatch with the same controllers the generator would import. Point the kernel at FakeDatabase when a page loads models.
import { NextAdapter, Route, page } from '@avelon/next'
import { createKernel } from '@avelon/core'
const adapter = new NextAdapter({ root: tempDir })
await adapter.mount(Route.manifest(), { kernel: createKernel() })
const Page = page(PostController, 'index', '/posts')
await Page({})
bun test
bun run typecheck