@aventara/next 0.0.0-stage → 1.0.0-rc.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,59 @@
1
+ # @aventara/next
2
+
3
+ ## 1.0.0-rc.9
4
+
5
+ ### Patch Changes
6
+
7
+ - 09c5703: New package `@aventara/next`: hosts an Aventara Framework in a Next.js App Router application (Next `>=16.4 <17`,
8
+ Node runtime). It is in the fixed version group, released at the same version as every other package. `react` (`^19`)
9
+ is a peer, and `@aventara/mcp` an optional one. The entry is server-only.
10
+
11
+ - `Aventara({ ...aventaraConfig, adapter, state })` takes the configuration `createFramework` takes, flat. `adapter`
12
+ may be a function that builds the adapter (it may return a promise), and `state` (`({ headers, request }) => state`)
13
+ seeds each request's `ctx.$state`. It returns at once and builds nothing: the Framework is created on first use,
14
+ once.
15
+ - It returns `{ handlers, avClient, avApp, ready }`. `handlers` is `{ GET, POST, PUT, PATCH, DELETE }` for one
16
+ catch-all route file (`src/app/api/[...aventara]/route.ts`), serving the protocol's operation routes,
17
+ `GET /_contract` and `POST /_transactions`. `avClient` is the client layer for Server Components and Server Actions
18
+ (`framework.serverClient`, with this request's `state`). `avApp` is the application layer for trusted server code
19
+ (`framework.application`). `ready()` builds the Framework once and rejects with the construction error, so
20
+ `src/instrumentation.ts` can await it and a bad configuration fails `next start`.
21
+ - `Aventara({ framework, state })` hosts a Framework you built with `createFramework`, as
22
+ `AventaraModule.forRoot({ framework })` does in NestJS. A Framework and a configuration together are a type error,
23
+ and are refused when `Aventara()` is called.
24
+ - `access`: an access provider (from `@aventara/core`). Its `policy` checks each request and its `state` seeds
25
+ `ctx.$state`, called with the same `{ headers, request }`. It does not go with `state` or `policy`.
26
+ - `aventara-build --client` writes `@/aventara/client` (the browser client) and `@/aventara/universal`, whose `avClient`
27
+ calls `/api` in the browser and answers the same request in process on the server. `avDehydrate` (this package) and
28
+ `avHydrate` (`@/aventara/client`) carry `Date`, `bigint`, `Uint8Array` and decimal values from a Server Component to
29
+ a Client Component, through TanStack Query's `serializeData`/`deserializeData` or an SWR `fallback`.
30
+ - With `mcp` configured, the same `handlers` serve `@aventara/mcp`'s endpoint and its OAuth protected-resource
31
+ metadata, each from a one-line route file. On Next.js `mcp` needs `authorization`: without it the configuration is
32
+ a type error on the `Aventara({...})` call, or refused when the Framework is built. The endpoint is named at the
33
+ origin the agent called (`X-Forwarded-Host` and `X-Forwarded-Proto` when present, else `Host`).
34
+ - Every copy of `src/aventara/server.ts` that Next.js evaluates in one server process shares one Framework per `api`
35
+ path, so `ready()` in `instrumentation.ts` builds the one the routes use. In production, a different configuration
36
+ on a path already held is refused when `Aventara()` is called; in development it replaces the held one.
37
+ - `serverExternalPackages: ["@aventara/core", "@aventara/prisma7-adapter"]` in `next.config.ts` is required: Node.js
38
+ then loads core and the adapter once per server process instead of a copy per layer, so every layer gets values
39
+ and errors of the same classes and one Prisma client. `aventara new --host next` and `aventara init` add it, or
40
+ print it when they cannot edit the config safely.
41
+ - A call reads the database, so it runs at request time: every `avClient`, `avApp` and `@/aventara/universal` call
42
+ first awaits Next's `io()` (`next/cache`). Under Cache Components (`cacheComponents: true`, create-next-app's
43
+ default) `next build` never runs one while it prerenders a page, and a page that calls one needs what any uncached
44
+ database read needs: the calling component inside `<Suspense>` (or a `loading.tsx`), `"use cache"` on a function
45
+ that makes the call, or `export const instant = false`. Without one, `next build` fails with Next's own message
46
+ naming the three. In a request, inside `"use cache"`, in a Server Action, in an `after()` callback and without
47
+ Cache Components the call runs at once.
48
+ - `AvNextHandlers` (the type of `handlers`), `AvNextClientTransaction` (`avClient.transaction`) and
49
+ `AvNextAppTransaction` (`avApp.transaction`) are exported, so a project that emits declarations
50
+ (`declaration: true`) can write `export const { handlers, avClient, avApp, ready } = Aventara(...)` with no
51
+ annotation. `AvNextStateContext`, what the `state` option is handed (`{ headers, request? }`), is exported too,
52
+ for a `state` function written in a file of its own.
53
+
54
+ - Updated dependencies [205a379]
55
+ - Updated dependencies [a0bb05e]
56
+ - Updated dependencies [86ab319]
57
+ - Updated dependencies [fdf52e6]
58
+ - @aventara/core@1.0.0-rc.9
59
+ - @aventara/mcp@1.0.0-rc.9
package/LICENSE ADDED
@@ -0,0 +1,91 @@
1
+ Required Notice: Copyright 2026 Mohamed Ragheb
2
+
3
+ # PolyForm Shield License 1.0.0
4
+
5
+ <https://polyformproject.org/licenses/shield/1.0.0>
6
+
7
+ ## Acceptance
8
+
9
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
14
+
15
+ ## Distribution License
16
+
17
+ The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
18
+
19
+ ## Notices
20
+
21
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
22
+
23
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
24
+
25
+ ## Changes and New Works License
26
+
27
+ The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
28
+
29
+ ## Patent License
30
+
31
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
32
+
33
+ ## Noncompete
34
+
35
+ Any purpose is a permitted purpose, except for providing any product that competes with the software or any product the licensor or any of its affiliates provides using the software.
36
+
37
+ ## Competition
38
+
39
+ Goods and services compete even when they provide functionality through different kinds of interfaces or for different technical platforms. Applications can compete with services, libraries with plugins, frameworks with development tools, and so on, even if they're written in different programming languages or for different computer architectures. Goods and services compete even when provided free of charge. If you market a product as a practical substitute for the software or another product, it definitely competes.
40
+
41
+ ## New Products
42
+
43
+ If you are using the software to provide a product that does not compete, but the licensor or any of its affiliates brings your product into competition by providing a new version of the software or another product using the software, you may continue using versions of the software available under these terms beforehand to provide your competing product, but not any later versions.
44
+
45
+ ## Discontinued Products
46
+
47
+ You may begin using the software to compete with a product or service that the licensor or any of its affiliates has stopped providing, unless the licensor includes a plain-text line beginning with `Licensor Line of Business:` with the software that mentions that line of business. For example:
48
+
49
+ > Licensor Line of Business: YoyodyneCMS Content Management System (http://example.com/cms)
50
+
51
+ ## Sales of Business
52
+
53
+ If the licensor or any of its affiliates sells a line of business developing the software or using the software to provide a product, the buyer can also enforce [Noncompete](#noncompete) for that product.
54
+
55
+ ## Fair Use
56
+
57
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
58
+
59
+ ## No Other Rights
60
+
61
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
62
+
63
+ ## Patent Defense
64
+
65
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
66
+
67
+ ## Violations
68
+
69
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
70
+
71
+ ## No Liability
72
+
73
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
74
+
75
+ ## Definitions
76
+
77
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
78
+
79
+ A **product** can be a good or service, or a combination of them.
80
+
81
+ **You** refers to the individual or entity agreeing to these terms.
82
+
83
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all its affiliates.
84
+
85
+ **Affiliates** means the other organizations than an organization has control over, is under the control of, or is under common control with.
86
+
87
+ **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
88
+
89
+ **Your licenses** are all the licenses granted to you for the software under these terms.
90
+
91
+ **Use** means anything you do with the software requiring one of your licenses.
@@ -0,0 +1,9 @@
1
+ # Additional Permission — Aventara
2
+
3
+ Copyright 2026 Mohamed Ragheb
4
+
5
+ In addition to the permissions of the PolyForm Shield License 1.0.0 in `LICENSE`, the licensor grants the following additional permission:
6
+
7
+ The Noncompete section applies only to providing a product that competes with the software itself — the Aventara framework, its packages and its tooling. Providing a product that competes with any other product the licensor or its affiliates provide using the software is a permitted purpose.
8
+
9
+ This additional permission only adds to your permissions; it does not restrict or replace any term of `LICENSE`.
package/README.md CHANGED
@@ -1,3 +1,305 @@
1
- # Temporary Holding Version
1
+ # `@aventara/next`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Hosts an Aventara Framework in a Next.js App Router application (Next 16.4 or later 16.x, Node runtime). It serves the
4
+ framework's HTTP protocol (`AvProtocol.bind(framework)` from `@aventara/core/protocol`) from one catch-all route file.
5
+ Every rule of the protocol (routes, decoding, statuses, envelopes, headers, absence) is core's; this package only turns
6
+ a Web `Request` into the protocol's request and writes the protocol's answer back as a Web `Response`.
7
+
8
+ `@aventara/core` is its dependency; `next` (`>=16.4 <17`) and `react` (`^19`) are peers, and `@aventara/mcp` an
9
+ optional one (see MCP). The entry is server-only: a Client Component
10
+ importing it fails the Next.js build.
11
+
12
+ ## Setup
13
+
14
+ ```ts
15
+ // src/aventara/server.ts
16
+ import "server-only";
17
+ import { Aventara } from "@aventara/next";
18
+ import { createPrismaAdapter } from "@aventara/prisma7-adapter";
19
+ import { prisma } from "../prisma";
20
+ import adapter from "./adapter";
21
+ import { aventaraConfig } from "./config";
22
+
23
+ export const { handlers, avClient, avApp, ready } = Aventara({
24
+ ...aventaraConfig, // api: "/api", resources, policy, ...
25
+ adapter: () => createPrismaAdapter({ ...adapter, client: prisma }),
26
+ state: async ({ headers }) => ({ user: await userFrom(headers) }), // your auth library
27
+ });
28
+ ```
29
+
30
+ ```ts
31
+ // src/app/api/[...aventara]/route.ts (the folder matches api: "/api")
32
+ import { handlers } from "@/aventara/server";
33
+
34
+ export const { GET, POST, PUT, PATCH, DELETE } = handlers;
35
+ ```
36
+
37
+ | Option | What it is |
38
+ |---|---|
39
+ | everything `createFramework` takes | `api`, `resources`, `policy`, `limits`, `prebuilt`, ... checked exactly as `createFramework` checks them |
40
+ | `adapter` | the adapter, or a function that builds it (it may return a promise), called on the first request |
41
+ | `state` | `({ headers, request }) => state`: seeds each request's `ctx.$state` (where `policy.principal` reads the user) |
42
+ | `access` | an access provider (see `@aventara/core`): its `policy` checks each request and its `state` seeds `ctx.$state`, handed the same `{ headers, request }`. Not together with `state` or `policy` |
43
+
44
+ Or hand it a Framework you built, as `createFramework` resolved it (as `AventaraModule.forRoot({ framework })` takes
45
+ one in NestJS), with `state` beside it (or none, when it was built with `access`):
46
+
47
+ ```ts
48
+ const framework = await createFramework({ ...aventaraConfig, adapter: createPrismaAdapter({ ...adapter, client }) });
49
+ export const { handlers, avClient, avApp, ready } = Aventara({ framework, state });
50
+ ```
51
+
52
+ Every member works on that Framework, which is never built again: `avClient.tx` and `avApp.appTx` build handles at
53
+ once, and `ready()` checks it (a Resource named like a member, `mcp` without `authorization`) and registers the
54
+ universal path. A Framework and a configuration are two sources of one Framework, so the options carry one or the
55
+ other: a configuration key beside `framework` is a type error, and `Aventara()` refuses it when called, naming the
56
+ keys. So is a `framework` that is not one.
57
+
58
+ `Aventara()` returns at once and builds nothing: `next build` imports the route module, so the Framework (and the
59
+ adapter, when it is a function) is created on first use, or by `ready()`, once. A configuration `createFramework`
60
+ refuses rejects that use with its `AvConstructionError`.
61
+
62
+ | Member | What it is |
63
+ |---|---|
64
+ | `handlers` | `{ GET, POST, PUT, PATCH, DELETE }` for the catch-all route file |
65
+ | `avClient` | the client layer for Server Components and Server Actions: `framework.serverClient`, with this request's `state`; `avClient.tx` and `avClient.transaction([...])` |
66
+ | `avApp` | the application layer for trusted server code: `framework.application`; `avApp.appTx` and `avApp.transaction([...])` |
67
+ | `ready` | `() => Promise<void>`: builds the Framework once; rejects with the construction error; registers the in-process path `@/aventara/universal` calls on the server |
68
+
69
+ `handlers` is typed `AvNextHandlers`, and `avClient.transaction` and `avApp.transaction` `AvNextClientTransaction` and
70
+ `AvNextAppTransaction`, all three exported, so a project that emits declarations (`declaration: true`) writes
71
+ `export const { handlers, avClient, avApp, ready } = Aventara(...)` with no annotation. `AvNextStateContext` is what
72
+ `state` is handed (`{ headers, request? }`), for a `state` function written in a file of its own:
73
+ `export const state = async ({ headers }: AvNextStateContext) => ({ user: await userOf(headers) });`.
74
+
75
+ ## Server Components and Server Actions
76
+
77
+ ```tsx
78
+ // src/app/campaigns/page.tsx
79
+ import { Suspense } from "react";
80
+ import { avClient } from "@/aventara/server";
81
+
82
+ async function Campaigns() {
83
+ const campaigns = await avClient.Campaign.find.many({
84
+ where: { startsAt: { gt: new Date() } },
85
+ select: ["id", "name", "startsAt"],
86
+ });
87
+ return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;
88
+ }
89
+
90
+ export default function Page() {
91
+ return (
92
+ <Suspense fallback={<p>Loading…</p>}>
93
+ <Campaigns />
94
+ </Suspense>
95
+ );
96
+ }
97
+ ```
98
+
99
+ `avClient.<Resource>.<family>.<variant>(args, options?)` is `framework.serverClient`: typed from the Client
100
+ Contract's view (what the client layer hides is absent), taking and answering application values (`Date`, `bigint`,
101
+ `AvDecimal`, `Uint8Array`). A call runs in process, with no HTTP: the same client pipelines, validation, exposure and
102
+ policy as a request to `handlers`, with the principal `policy.principal` reads from `state`. A refusal is thrown as
103
+ its `AvError` class (`AvAuthError` for the policy).
104
+
105
+ `state` is called with `{ headers }` from `next/headers` (there is no `request` outside a route call), once per
106
+ server render however many calls the render makes. Outside a render, in a Server Action's body for example, it runs
107
+ once per call. A call that names its own `state` in its options uses it as given. Without a `state` option, calls
108
+ start from `{}` and read no headers.
109
+
110
+ ### Request-time data
111
+
112
+ A call reads the database, so Next.js renders it at request time, never into the page `next build` prerenders. Every
113
+ `avClient`, `avApp` and `@/aventara/universal` call first awaits Next's `io()` (`next/cache`). Under Cache Components
114
+ (`cacheComponents: true`, which create-next-app sets), a page that calls one needs what any uncached database read
115
+ needs, one of:
116
+
117
+ - `<Suspense>` around the component that calls it (or a `loading.tsx` beside the page): the fallback is prerendered and
118
+ the data streams in at request time, as above;
119
+ - `"use cache"` on a function that makes the call: the result is cached, and prerendered with the page;
120
+ - `export const instant = false` in the page: it renders whole, at request time.
121
+
122
+ With none of them, `next build` fails with Next's own message, which names the three. In a request, inside
123
+ `"use cache"`, in a Server Action, in an `after()` callback and without Cache Components the call runs at once.
124
+
125
+ `avApp` is `framework.application`: trusted server code. It reads no request, and the policy checks a call only when
126
+ it names a principal (`{ as: principal }`).
127
+
128
+ Transactions keep their layer: `avClient.tx` builds client-layer handles (`framework.serverClientTx`) and
129
+ `avClient.transaction([...])` runs them with this request's state; `avApp.appTx` builds application handles (and
130
+ `avApp.appTx.$sql`), and `avApp.transaction([...])` runs them. A list that mixes the two is a type error, and refused
131
+ at runtime (`A2004`). Handles are built on the built Framework: `await ready()` before building them.
132
+
133
+ ## Startup
134
+
135
+ ```ts
136
+ // src/instrumentation.ts
137
+ export async function register() {
138
+ if (process.env.NEXT_RUNTIME === "nodejs") {
139
+ const { ready } = await import("@/aventara/server");
140
+ await ready();
141
+ }
142
+ }
143
+ ```
144
+
145
+ `ready()` builds the Framework and its routes, once: a second call returns the same promise. It rejects with the
146
+ construction error, so a bad configuration fails `next start`. It reads no request, so it runs where there is none
147
+ (`instrumentation.ts`, a script).
148
+
149
+ ### `serverExternalPackages` (required)
150
+
151
+ ```ts
152
+ // next.config.ts
153
+ const nextConfig: NextConfig = {
154
+ serverExternalPackages: ["@aventara/core", "@aventara/prisma7-adapter"],
155
+ };
156
+ ```
157
+
158
+ Next.js bundles a package into each layer of the server (instrumentation, route handlers, Server Components) as a
159
+ copy of its own. Listed here, `@aventara/core` and the adapter are loaded by Node.js once per server process instead:
160
+ one core, so the Framework every layer shares (below) answers each layer with values and errors of the same classes
161
+ (`AvDecimal`, `AvValidationError`, ...), and one Prisma client. `aventara new --host next` and `aventara init` add the
162
+ line (an existing config they cannot edit safely gets it printed instead). `@aventara/next` itself stays bundled: it
163
+ relies on Next.js's server aliases (`next/navigation`, `next/headers`, `server-only`).
164
+
165
+ Next.js evaluates `src/aventara/server.ts` more than once in one server process (once for `instrumentation.ts`, again
166
+ for the routes and pages). Every copy that calls `Aventara()` with the same options shares one Framework, held per
167
+ `api` path for the process, so `ready()` in `instrumentation.ts` builds the one the routes use, with one adapter, and
168
+ `avClient.tx` works from the first request. Two instances on two `api` paths hold two. In production, a different
169
+ configuration on a path already held is refused when `Aventara()` is called; in development it replaces the held one,
170
+ as `next dev` evaluates the file again after an edit. A Framework given as `{ framework }` is that call's own.
171
+
172
+ Once the Framework is built, `ready()` also registers the path `@/aventara/universal`'s `avClient` takes on the server
173
+ (below). Without `instrumentation.ts`, that `avClient` refuses its calls on the server in one sentence.
174
+
175
+ ## Prefetch and hydrate: `@/aventara/universal`, `avDehydrate`, `avHydrate`
176
+
177
+ `aventara-build --client` writes `src/aventara/universal.ts`. Its `avClient` has the browser client's types and runs on
178
+ both sides: in the browser it calls `/api`; on the server it answers the same request in process, through the bound
179
+ protocol and with this request's `state`, so a query prefetched in a Server Component and refetched in the browser
180
+ gets the same values (`Date`, `bigint`, the client's own `AvDecimal`).
181
+
182
+ A decimal is a class instance, which React refuses to pass from a Server Component to a Client Component. `avDehydrate`
183
+ (this package, server side) turns each `Date`, `bigint`, `Uint8Array` and decimal into a tagged plain object, and
184
+ `avHydrate` (`@/aventara/client`, browser side) turns them back.
185
+
186
+ ```ts
187
+ // src/app/notes/notes.queries.ts (server and browser)
188
+ import { queryOptions } from "@tanstack/react-query";
189
+ import { avClient } from "@/aventara/universal";
190
+
191
+ export const notesQuery = queryOptions({
192
+ queryKey: ["notes"],
193
+ queryFn: () => avClient.Note.find.many({ select: ["id", "title", "createdAt"] }),
194
+ });
195
+ ```
196
+
197
+ ```tsx
198
+ // src/app/notes/page.tsx (Server Component)
199
+ import { dehydrate, HydrationBoundary, QueryClient } from "@tanstack/react-query";
200
+ import { Suspense } from "react";
201
+ import { avDehydrate } from "@aventara/next";
202
+ import { notesQuery } from "./notes.queries";
203
+ import { NoteList } from "./note-list";
204
+
205
+ async function Notes() {
206
+ const queryClient = new QueryClient();
207
+ await queryClient.prefetchQuery(notesQuery);
208
+ return (
209
+ <HydrationBoundary state={dehydrate(queryClient, { serializeData: avDehydrate })}>
210
+ <NoteList />
211
+ </HydrationBoundary>
212
+ );
213
+ }
214
+
215
+ export default function Page() {
216
+ return (
217
+ <Suspense fallback={<p>Loading…</p>}>
218
+ <Notes />
219
+ </Suspense>
220
+ );
221
+ }
222
+ ```
223
+
224
+ ```tsx
225
+ // src/app/providers.tsx
226
+ "use client";
227
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
228
+ import { avHydrate } from "@/aventara/client";
229
+
230
+ const queryClient = new QueryClient({ defaultOptions: { hydrate: { deserializeData: avHydrate } } });
231
+ export const Providers = ({ children }: { children: React.ReactNode }) => (
232
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
233
+ );
234
+ ```
235
+
236
+ With SWR, pass `avDehydrate({ [key]: data })` from the Server Component and `avHydrate(fallback)` to `SWRConfig`'s
237
+ `fallback` in the Client Component.
238
+
239
+ Importing `@/aventara/client` instead of `@/aventara/universal` in shared code opts out of running on the server: its
240
+ calls are refused there.
241
+
242
+ ## Routes
243
+
244
+ `handlers` answers the protocol's routes under `api`: each operation route (`POST /api/_resources/...`),
245
+ `GET /api/_contract` (with its `ETag`, and `304` on `If-None-Match`) and `POST /api/_transactions` when the
246
+ ClientContract's transactions are interactive. Every method is exported so that a wrong method on an operation route
247
+ gets the protocol's `405` (`A2012`) with `Allow`. A path the protocol does not own goes to Next's `notFound()`.
248
+
249
+ A request body is read up to `limits.maxRequestBytes` decoded bytes; `gzip`, `deflate` and `br` bodies are inflated,
250
+ and any other content coding is the protocol's `415` (`A2011`).
251
+
252
+ ## MCP
253
+
254
+ With `mcp` in the configuration, the same `handlers` also serve `@aventara/mcp`'s endpoint and its OAuth
255
+ protected-resource metadata. Add one route file for each path:
256
+
257
+ ```ts
258
+ // src/aventara/server.ts
259
+ export const { handlers } = Aventara({
260
+ ...aventaraConfig,
261
+ mcp: {
262
+ path: "/mcp",
263
+ authorization: {
264
+ servers: ["https://auth.example.com"],
265
+ verify: async (token) => (await verifyAccessToken(token)) ?? null, // your token check
266
+ },
267
+ },
268
+ adapter: () => createPrismaAdapter({ ...adapter, client: prisma }),
269
+ });
270
+ ```
271
+
272
+ ```ts
273
+ // src/app/mcp/route.ts
274
+ import { handlers } from "@/aventara/server";
275
+
276
+ export const { GET, POST, DELETE } = handlers;
277
+ ```
278
+
279
+ ```ts
280
+ // src/app/.well-known/oauth-protected-resource/mcp/route.ts
281
+ import { handlers } from "@/aventara/server";
282
+
283
+ export const { GET } = handlers;
284
+ ```
285
+
286
+ Install `@aventara/mcp` at the same version as `@aventara/core` (`npm install --save-exact @aventara/mcp@<version>`).
287
+ It is an optional peer, loaded only when `mcp` is configured; when it is missing, the first request fails with a
288
+ sentence that names the install command.
289
+
290
+ On Next.js, `mcp` needs `authorization`. Without it, `@aventara/mcp` answers only callers on the same machine that
291
+ send no `x-forwarded-for`, and Next.js adds `x-forwarded-for` to every request, so every caller would be refused. An
292
+ `mcp` without `authorization` is a type error on the `Aventara({...})` call, and, for a configuration typed wider than its literal, refused when the Framework is built (the first request), with a sentence that says so.
293
+
294
+ Everything the endpoint answers is `@aventara/mcp`'s: the bearer check and its `401` with `WWW-Authenticate`, the
295
+ tools, the tool calls (each one an agent-origin protocol request, with `state` and the token's claims in
296
+ `ctx.$state.mcp.claims`), the `405` on `GET` and `DELETE` (the endpoint is stateless) and the metadata document. The
297
+ request body is read up to the Agent Contract's `maxRequestBytes` and handed on as sent.
298
+
299
+ ## Status
300
+
301
+ A prerelease, installed with its tag:
302
+
303
+ ```bash
304
+ npm install @aventara/next@rc
305
+ ```
@@ -0,0 +1,155 @@
1
+ import { type AvAdapter, type AvAdapterModel, type AvConfig, type AvConfigAny, type AvConfigExact, type AvFramework, type AvState, createFramework } from "@aventara/core";
2
+ import { type AvNextHandlers, type AvNextStateContext } from "./route-handlers.factory.js";
3
+ import { type AvNextAppTransaction, type AvNextClientTransaction } from "./server-operations.factory.js";
4
+ /** `Aventara()`'s options: a configuration whose adapter may be built on first use, and `state`. */
5
+ type AventaraOptions<M extends AvAdapterModel, Cfg> = Omit<AvConfig<M, Cfg>, "adapter"> & HostOptions<M>;
6
+ /** The two host keys: an adapter that may be built on first use, and `state`. */
7
+ type HostOptions<M extends AvAdapterModel> = {
8
+ /**
9
+ * The adapter, or a function that builds it (it may return a promise),
10
+ * called once, on the first request.
11
+ */
12
+ readonly adapter: AvAdapter<M> | (() => AvAdapter<M> | PromiseLike<AvAdapter<M>>);
13
+ /**
14
+ * Seeds a request's `ctx.$state`, once per request, from its headers and,
15
+ * on a route call, the route handler's request. A throw is answered like
16
+ * any developer failure: a thrown `AvError` as its own outcome, anything
17
+ * else as an internal failure. Omitted, the state is `{}`, or what the
18
+ * `access` provider's `state` seeds; not together with `access`.
19
+ */
20
+ state?(context: AvNextStateContext): AvState | Promise<AvState>;
21
+ };
22
+ /**
23
+ * On Next.js, `mcp` needs `authorization`: Next adds `x-forwarded-for` to
24
+ * every request, so an endpoint without it would refuse every caller. The
25
+ * expected type of an `mcp` written without it; `undefined` otherwise.
26
+ */
27
+ type McpOnNext<Cfg> = Cfg extends {
28
+ readonly mcp: infer Endpoint;
29
+ } ? [Endpoint] extends [undefined] ? undefined : string extends Endpoint ? undefined : Endpoint extends string ? {
30
+ readonly "On Next.js, `mcp` needs `authorization` ({ path, authorization: { servers, verify } }): Next.js adds x-forwarded-for to every request, so an MCP endpoint without it would refuse every caller.": true;
31
+ } : "authorization" extends keyof Endpoint ? undefined : {
32
+ readonly "On Next.js, `mcp` needs `authorization` ({ path, authorization: { servers, verify } }): Next.js adds x-forwarded-for to every request, so an MCP endpoint without it would refuse every caller.": true;
33
+ readonly path?: unknown;
34
+ } : undefined;
35
+ /** `state` refused beside `access`, by the sentence that says why. */
36
+ type StateBesideAccess<Cfg> = Cfg extends {
37
+ readonly access: object;
38
+ } ? {
39
+ readonly state?: readonly [
40
+ "Error: `access` seeds each request's state; remove `state`"
41
+ ];
42
+ } : unknown;
43
+ /** The options as `createFramework` reads them: `state` taken out. */
44
+ type ConfigOf<Cfg> = Cfg extends {
45
+ readonly state: (...context: never) => unknown;
46
+ } ? Omit<Cfg, "state"> : Cfg;
47
+ /** The Framework `Aventara()` builds from its options. */
48
+ type HostedFramework<M extends AvAdapterModel, Cfg> = Awaited<ReturnType<typeof createFramework<M, ConfigOf<Cfg>>>>;
49
+ /**
50
+ * `Aventara({ framework, state })`'s options: a Framework `createFramework`
51
+ * built, and `state`. A configuration key beside `framework` is refused.
52
+ */
53
+ type FrameworkOptions = {
54
+ /** A Framework built elsewhere: what `createFramework` resolved to. */
55
+ readonly framework: AvFramework;
56
+ /** A configuration's: refused beside `framework`. */
57
+ readonly adapter?: never;
58
+ state?(context: AvNextStateContext): AvState | Promise<AvState>;
59
+ };
60
+ /** The Framework the options carry under `framework`, else the one they configure. */
61
+ type GivenFramework<M extends AvAdapterModel, Cfg> = Cfg extends {
62
+ readonly framework: infer Given extends HostedMembers;
63
+ } ? Given : HostedFramework<M, Cfg>;
64
+ /** The four members of a Framework the host's members are typed from. */
65
+ type HostedMembers = {
66
+ readonly serverClient: unknown;
67
+ readonly serverClientTx: unknown;
68
+ readonly application: unknown;
69
+ readonly appTx: unknown;
70
+ };
71
+ /** What `Aventara()` returns: the members over its Framework. */
72
+ type AventaraHostOf<F extends HostedMembers> = {
73
+ /**
74
+ * `{ GET, POST, PUT, PATCH, DELETE }` for the catch-all route file under the
75
+ * configured `api` path: `export const { GET, POST, PUT, PATCH, DELETE } = handlers;`
76
+ */
77
+ readonly handlers: AvNextHandlers;
78
+ /**
79
+ * The client layer for Server Components and Server Actions:
80
+ * `avClient.<resource>.<family>.<variant>(args, options?)`, the Framework's
81
+ * `serverClient`. Arguments and results are application values (a `Date`,
82
+ * a `bigint`, an `AvDecimal`); what the client layer hides is absent; the
83
+ * policy checks the principal `state` gives, the `state` run once per
84
+ * request with the headers of `next/headers`. A direct call: no HTTP.
85
+ *
86
+ * `avClient.tx` builds client-layer transaction handles and
87
+ * `avClient.transaction([...])` runs them with the same state.
88
+ */
89
+ readonly avClient: F["serverClient"] & {
90
+ /**
91
+ * Transaction handles under the client layer's rules, taking application
92
+ * values: `avClient.tx.<resource>.<family>.<variant>(args)`. Built on the
93
+ * built Framework: after `await ready()`.
94
+ */
95
+ readonly tx: F["serverClientTx"];
96
+ /**
97
+ * Runs `avClient.tx` handles as one transaction with this request's
98
+ * state: `avClient.transaction([h1, h2, …], options?)`.
99
+ */
100
+ readonly transaction: AvNextClientTransaction;
101
+ };
102
+ /**
103
+ * The application layer, for trusted server code:
104
+ * `avApp.<resource>.<family>.<variant>(args, options?)`, the Framework's
105
+ * `application`. The policy checks a call only when it names a principal
106
+ * (`{ as }`).
107
+ *
108
+ * `avApp.appTx` builds application transaction handles and
109
+ * `avApp.transaction([...])` runs them.
110
+ */
111
+ readonly avApp: F["application"] & {
112
+ /**
113
+ * Transaction handles under the application layer's rules:
114
+ * `avApp.appTx.<resource>.<family>.<variant>(args)`, and `avApp.appTx.$sql`.
115
+ * Built on the built Framework: after `await ready()`.
116
+ */
117
+ readonly appTx: F["appTx"];
118
+ /**
119
+ * Runs `avApp.appTx` handles as one transaction:
120
+ * `avApp.transaction([h1, h2, …], options?)`.
121
+ */
122
+ readonly transaction: AvNextAppTransaction;
123
+ };
124
+ /**
125
+ * Builds the Framework and its routes, once; rejects with the construction
126
+ * error. `src/instrumentation.ts` awaits it at startup, so a bad
127
+ * configuration fails `next start`. Reads no request: it runs outside one.
128
+ * Once built, it registers the in-process path `@/aventara/universal`'s
129
+ * `avClient` calls on the server, with each request's state.
130
+ */
131
+ readonly ready: () => Promise<void>;
132
+ };
133
+ /**
134
+ * Hosts an Aventara Framework in a Next.js App Router application. Returns at
135
+ * once and builds nothing: the Framework is created on first use, or by
136
+ * `ready()`, once.
137
+ *
138
+ * ```ts
139
+ * export const { handlers, avClient, avApp, ready } = Aventara({
140
+ * ...aventaraConfig,
141
+ * adapter: () => createPrismaAdapter({ ...adapter, client: prisma }),
142
+ * state: async ({ headers }) => ({ user: await userOf(headers) }),
143
+ * });
144
+ * ```
145
+ *
146
+ * Or a Framework built elsewhere, as `createFramework` resolved it (with
147
+ * `state` beside it, and no configuration key):
148
+ * `Aventara({ framework, state })`.
149
+ */
150
+ export declare function Aventara<M extends AvAdapterModel, const Cfg = AvConfigAny>(options: Cfg extends {
151
+ readonly framework: object;
152
+ } ? FrameworkOptions : McpOnNext<Cfg> extends object ? Omit<Cfg, "mcp"> & HostOptions<M> & {
153
+ readonly mcp: McpOnNext<Cfg>;
154
+ } & StateBesideAccess<Cfg> : Cfg & AvConfigExact<M, ConfigOf<Cfg>> & AventaraOptions<M, ConfigOf<Cfg>> & StateBesideAccess<Cfg>): AventaraHostOf<GivenFramework<M, Cfg>>;
155
+ export {};