@aventara/next 1.0.0-rc.10 → 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 +0 -49
- package/README.md +1 -19
- package/dist/aventara.factory.js +3 -3
- package/dist/host-bootstrap.validator.d.ts +3 -14
- package/dist/host-bootstrap.validator.js +2 -7
- package/dist/protocol-route.table.d.ts +2 -3
- package/dist/protocol-route.table.js +0 -1
- package/dist/request-body.reader.d.ts +1 -2
- package/dist/server-operations.factory.d.ts +2 -4
- package/dist/server-operations.factory.js +3 -62
- package/dist/shared-framework.registry.d.ts +5 -5
- package/dist/shared-framework.registry.js +6 -16
- package/dist/universal-path.registry.d.ts +2 -5
- package/dist/universal-path.registry.js +2 -7
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,54 +1,5 @@
|
|
|
1
1
|
# @aventara/next
|
|
2
2
|
|
|
3
|
-
## 1.0.0-rc.10
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- 04bb39a: `engines.node` is now `^22.23.4 || >=24.22.1`, the Node security releases of 2026-10-20 (CVE-2026-105481,
|
|
8
|
-
CVE-2026-105482, CVE-2026-105483, CVE-2026-106069, CVE-2026-106072, CVE-2026-106073, CVE-2026-106074,
|
|
9
|
-
CVE-2026-111497). The bins refuse an older Node in one sentence naming that range.
|
|
10
|
-
- 587dd01: The route handlers answer `POST {api}/_access/can` when the Framework is built with `access`, through
|
|
11
|
-
`AvProtocol.bind(framework).handleCan`, with the request's state seeded as on every other protocol route.
|
|
12
|
-
- d7e7369: `avClient.$access`: `framework.serverClient.$access` with the request filled in. `can` is answered under the
|
|
13
|
-
request's state; `me()` and the other Aventara Access verbs run in process with the headers of `next/headers` (a
|
|
14
|
-
Server Action's sign-in sets its cookie through Access itself). `ready()` registers the verbs for
|
|
15
|
-
`@/aventara/universal` on the server.
|
|
16
|
-
- 7ee547b: `avClient.$access` carries Aventara Access's `kiosk` verbs (`switchUser`, `lock`, `setPin`, `enrollDevice`,
|
|
17
|
-
`revokeDevice`) when the provider states them, run in process with the headers of `next/headers`, so a Server Action's
|
|
18
|
-
switch sets the rotated device cookie through Access itself; `ready()` registers them for `@/aventara/universal`.
|
|
19
|
-
- 88263a1: Documentation: `$access.can([])` answers `[]`, not a refusal; the MCP README states the query and fragment rule for
|
|
20
|
-
every authorization server.
|
|
21
|
-
- 15f3402: `avClient.$access` holds only the verbs the access provider offers, as `serverClient.$access` does. Calling another one
|
|
22
|
-
rejects with `AvProtocolError` `A2002`, naming why (no `access`, or a provider that does not offer that verb), instead
|
|
23
|
-
of a `TypeError`.
|
|
24
|
-
|
|
25
|
-
In `next dev`, an edit that changes only a function (a guard's or a pipe's body, the adapter factory) builds the
|
|
26
|
-
Framework again; it no longer needs a restart.
|
|
27
|
-
|
|
28
|
-
- Updated dependencies [587dd01]
|
|
29
|
-
- Updated dependencies [d7e7369]
|
|
30
|
-
- Updated dependencies [7ee547b]
|
|
31
|
-
- Updated dependencies [f52c499]
|
|
32
|
-
- Updated dependencies [2907484]
|
|
33
|
-
- Updated dependencies [a6bbdf0]
|
|
34
|
-
- Updated dependencies [3e26549]
|
|
35
|
-
- Updated dependencies [a6bbdf0]
|
|
36
|
-
- Updated dependencies [6f3f6f6]
|
|
37
|
-
- Updated dependencies [04bb39a]
|
|
38
|
-
- Updated dependencies [d7e7369]
|
|
39
|
-
- Updated dependencies [3fb3333]
|
|
40
|
-
- Updated dependencies [827ccb6]
|
|
41
|
-
- Updated dependencies [7f5abc3]
|
|
42
|
-
- Updated dependencies [88263a1]
|
|
43
|
-
- Updated dependencies [10341d5]
|
|
44
|
-
- Updated dependencies [8129844]
|
|
45
|
-
- Updated dependencies [88263a1]
|
|
46
|
-
- Updated dependencies [3020b5c]
|
|
47
|
-
- Updated dependencies [88263a1]
|
|
48
|
-
- Updated dependencies [88263a1]
|
|
49
|
-
- @aventara/core@1.0.0-rc.10
|
|
50
|
-
- @aventara/mcp@1.0.0-rc.10
|
|
51
|
-
|
|
52
3
|
## 1.0.0-rc.9
|
|
53
4
|
|
|
54
5
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -122,21 +122,6 @@ needs, one of:
|
|
|
122
122
|
With none of them, `next build` fails with Next's own message, which names the three. In a request, inside
|
|
123
123
|
`"use cache"`, in a Server Action, in an `after()` callback and without Cache Components the call runs at once.
|
|
124
124
|
|
|
125
|
-
With `access`, `avClient.$access` is `framework.serverClient.$access` with this request filled in: `can([...])` is
|
|
126
|
-
answered under the request's state, and `me()`, `signIn(...)` and the other verbs the provider offers run Aventara
|
|
127
|
-
Access in process with the headers of `next/headers`. In a Server Action, Access sets a sign-in's cookie itself
|
|
128
|
-
(Better Auth's `nextCookies`). `@/aventara/universal`'s `$access` does the same on the server, through what `ready()`
|
|
129
|
-
registers, and calls Access's own routes in the browser. `$access` holds the verbs the provider offers, read from the
|
|
130
|
-
options before the Framework is built, as `serverClient.$access` holds them. Calling another one rejects with
|
|
131
|
-
`AvProtocolError` `A2002`, naming why: no `access`, or a provider whose `client` does not offer that verb.
|
|
132
|
-
|
|
133
|
-
On Access's `kiosk` profile, call `switchUser`, `lock` and `enrollDevice` from a Server Action (or from the browser),
|
|
134
|
-
never while rendering a Server Component: each rotates the device's credential cookie, which Next lets Access set only
|
|
135
|
-
in a Server Action or a Route Handler, so a rotation during a render is lost and the device's next switch is refused. Never call a verb that
|
|
136
|
-
changes anything (`signIn`, `signUp`, `signOut`, `resetPassword`, and the kiosk's five) inside `"use cache"`: a cached
|
|
137
|
-
result would replay one caller's answer to another and skip the call. Each verb's answer is projected onto its result
|
|
138
|
-
schema on the server too, so no session token or device secret reaches your code.
|
|
139
|
-
|
|
140
125
|
`avApp` is `framework.application`: trusted server code. It reads no request, and the policy checks a call only when
|
|
141
126
|
it names a principal (`{ as: principal }`).
|
|
142
127
|
|
|
@@ -182,10 +167,7 @@ for the routes and pages). Every copy that calls `Aventara()` with the same opti
|
|
|
182
167
|
`api` path for the process, so `ready()` in `instrumentation.ts` builds the one the routes use, with one adapter, and
|
|
183
168
|
`avClient.tx` works from the first request. Two instances on two `api` paths hold two. In production, a different
|
|
184
169
|
configuration on a path already held is refused when `Aventara()` is called; in development it replaces the held one,
|
|
185
|
-
as `next dev` evaluates the file again after an edit.
|
|
186
|
-
function's source in each layer). An edit that changes only a function, a guard's or a pipe's body or the adapter
|
|
187
|
-
factory, is told by each layer from the text that layer saw last: in development the first layer to see it builds the
|
|
188
|
-
Framework again, and the other layers join that build. A Framework given as `{ framework }` is that call's own.
|
|
170
|
+
as `next dev` evaluates the file again after an edit. A Framework given as `{ framework }` is that call's own.
|
|
189
171
|
|
|
190
172
|
Once the Framework is built, `ready()` also registers the path `@/aventara/universal`'s `avClient` takes on the server
|
|
191
173
|
(below). Without `instrumentation.ts`, that `avClient` refuses its calls on the server in one sentence.
|
package/dist/aventara.factory.js
CHANGED
|
@@ -10,7 +10,7 @@ export function Aventara(options) {
|
|
|
10
10
|
const checked = checkedOptions(options);
|
|
11
11
|
const { source } = checked;
|
|
12
12
|
const slot = source.kind === "config"
|
|
13
|
-
? frameworkSlot(String(options.api), optionsFingerprint(options)
|
|
13
|
+
? frameworkSlot(String(options.api), optionsFingerprint(options))
|
|
14
14
|
: { fingerprint: "", framework: undefined, built: undefined };
|
|
15
15
|
const held = (created) => {
|
|
16
16
|
refuseMemberClashes(created);
|
|
@@ -46,14 +46,14 @@ export function Aventara(options) {
|
|
|
46
46
|
const universalState = checked.state === undefined
|
|
47
47
|
? undefined
|
|
48
48
|
: async () => (await requestState()) ?? {};
|
|
49
|
-
const { avClient, avApp } = createServerOperations({ built: frameworkOf, current: () => slot.built }, requestState
|
|
49
|
+
const { avClient, avApp } = createServerOperations({ built: frameworkOf, current: () => slot.built }, requestState);
|
|
50
50
|
return {
|
|
51
51
|
handlers: createRouteHandlers(protocolOf, checked.state),
|
|
52
52
|
avClient,
|
|
53
53
|
avApp,
|
|
54
54
|
ready: () => {
|
|
55
55
|
readied ??= protocolOf().then(() => {
|
|
56
|
-
registerUniversalPath(universalPath(protocolOf, universalState)
|
|
56
|
+
registerUniversalPath(universalPath(protocolOf, universalState));
|
|
57
57
|
});
|
|
58
58
|
return readied;
|
|
59
59
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvConfigAny, AvFramework } from "@aventara/core";
|
|
2
2
|
import type { HostState } from "./route-handlers.factory.js";
|
|
3
3
|
/**
|
|
4
4
|
* Refuses an MCP endpoint without `authorization`: on Next.js it could answer
|
|
@@ -8,7 +8,7 @@ export declare function refuseOpenMcp(endpoint: {
|
|
|
8
8
|
readonly authorization?: unknown;
|
|
9
9
|
}): void;
|
|
10
10
|
/** The options, split into what the host keeps and where its Framework comes from. */
|
|
11
|
-
type CheckedOptions = {
|
|
11
|
+
export type CheckedOptions = {
|
|
12
12
|
/**
|
|
13
13
|
* What seeds each request's state: the developer's `state`, or the
|
|
14
14
|
* Framework's access provider's (`access`). Core never sees it.
|
|
@@ -16,19 +16,9 @@ type CheckedOptions = {
|
|
|
16
16
|
readonly state: HostState | undefined;
|
|
17
17
|
/** The Framework's source: one built elsewhere, or a configuration this host builds. */
|
|
18
18
|
readonly source: FrameworkSourceOption;
|
|
19
|
-
/**
|
|
20
|
-
* The Framework's access provider's `client`, known before the Framework
|
|
21
|
-
* is built (`null` for a provider without one), or `undefined` with no
|
|
22
|
-
* `access`: which verbs `avClient.$access` holds.
|
|
23
|
-
*/
|
|
24
|
-
readonly access: AccessClient | undefined;
|
|
25
|
-
};
|
|
26
|
-
/** What `avClient.$access` reads of an access provider: its `client`, or `null`. */
|
|
27
|
-
export type AccessClient = {
|
|
28
|
-
readonly client: AvAccessProvider["client"] | null;
|
|
29
19
|
};
|
|
30
20
|
/** `{ framework }`: a Framework `createFramework` built; or a configuration for it. */
|
|
31
|
-
type FrameworkSourceOption = {
|
|
21
|
+
export type FrameworkSourceOption = {
|
|
32
22
|
readonly kind: "framework";
|
|
33
23
|
readonly framework: AvFramework;
|
|
34
24
|
} | {
|
|
@@ -46,4 +36,3 @@ type FrameworkSourceOption = {
|
|
|
46
36
|
* of an adapter.
|
|
47
37
|
*/
|
|
48
38
|
export declare function checkedOptions(options: unknown): CheckedOptions;
|
|
49
|
-
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { accessStateOf } from "@aventara/core/internal";
|
|
2
2
|
const NOT_AN_OBJECT = "Aventara: the options are not an object; pass `{ ...aventaraConfig, adapter, state }`, or `{ framework, state }`.";
|
|
3
3
|
const NOT_A_FRAMEWORK = "Aventara: `framework` is not an Aventara Framework (it has no string `api` and no `contracts.client`); pass what `createFramework` resolved to.";
|
|
4
4
|
function bothForms(keys) {
|
|
@@ -34,13 +34,9 @@ export function checkedOptions(options) {
|
|
|
34
34
|
if (!isFramework(framework)) {
|
|
35
35
|
throw new Error(NOT_A_FRAMEWORK);
|
|
36
36
|
}
|
|
37
|
-
const provider = accessProviderOf(framework);
|
|
38
37
|
return {
|
|
39
|
-
state: oneStateSeed(boundState,
|
|
38
|
+
state: oneStateSeed(boundState, accessStateOf(framework)),
|
|
40
39
|
source: { kind: "framework", framework },
|
|
41
|
-
access: provider === undefined
|
|
42
|
-
? undefined
|
|
43
|
-
: { client: provider.client ?? null },
|
|
44
40
|
};
|
|
45
41
|
}
|
|
46
42
|
const access = config.access;
|
|
@@ -53,7 +49,6 @@ export function checkedOptions(options) {
|
|
|
53
49
|
? { ...config, adapter: await adapter() }
|
|
54
50
|
: config,
|
|
55
51
|
},
|
|
56
|
-
access: access === undefined ? undefined : { client: access.client ?? null },
|
|
57
52
|
};
|
|
58
53
|
}
|
|
59
54
|
function oneStateSeed(state, access) {
|
|
@@ -2,13 +2,13 @@ import type { AvState } from "@aventara/core";
|
|
|
2
2
|
import type { AvProtocolBinding, AvProtocolRequest, AvProtocolResponse } from "@aventara/core/protocol";
|
|
3
3
|
import type { McpBinding, McpRequest, McpRoute } from "./mcp-binding.loader.js";
|
|
4
4
|
/** One route `protocol.surface()` lists. */
|
|
5
|
-
type SurfaceRoute = ReturnType<AvProtocolBinding["surface"]>[number];
|
|
5
|
+
export type SurfaceRoute = ReturnType<AvProtocolBinding["surface"]>[number];
|
|
6
6
|
/** The developer's `state` option over one route request: seeds its `ctx.$state`. */
|
|
7
7
|
export type StateSeed = () => AvState | Promise<AvState>;
|
|
8
8
|
/** What a route answers its request with: a bound protocol member. */
|
|
9
9
|
export type RouteAnswer = (request: AvProtocolRequest, state: StateSeed | undefined) => AvProtocolResponse | Promise<AvProtocolResponse>;
|
|
10
10
|
/** The bound member each kind of surface route is answered by. */
|
|
11
|
-
type ProtocolRouteTable = {
|
|
11
|
+
export type ProtocolRouteTable = {
|
|
12
12
|
readonly [K in SurfaceRoute["kind"]]: (binding: AvProtocolBinding) => RouteAnswer;
|
|
13
13
|
};
|
|
14
14
|
export declare const PROTOCOL_ROUTES: ProtocolRouteTable;
|
|
@@ -18,4 +18,3 @@ export type McpRouteAnswer = (request: McpRequest, state: StateSeed | undefined)
|
|
|
18
18
|
export declare const MCP_ROUTES: {
|
|
19
19
|
readonly [K in McpRoute["kind"]]: (binding: McpBinding) => McpRouteAnswer;
|
|
20
20
|
};
|
|
21
|
-
export {};
|
|
@@ -2,7 +2,6 @@ export const PROTOCOL_ROUTES = {
|
|
|
2
2
|
operation: (binding) => (request, state) => binding.handleOperation(request, state === undefined ? {} : { state }),
|
|
3
3
|
contract: (binding) => (request) => binding.encodeContract(request),
|
|
4
4
|
transactions: (binding) => (request, state) => binding.handleTransaction(request, state === undefined ? {} : { state }),
|
|
5
|
-
can: (binding) => (request, state) => binding.handleCan(request, state === undefined ? {} : { state }),
|
|
6
5
|
};
|
|
7
6
|
export const MCP_ROUTES = {
|
|
8
7
|
endpoint: (binding) => (request, state) => binding.handleEndpoint(request, state === undefined ? {} : { state }),
|
|
@@ -3,7 +3,7 @@ import type { AvProtocolBinding, AvProtocolResponse } from "@aventara/core/proto
|
|
|
3
3
|
* What the body reader hands on: the stored bytes, an unsupported coding, or a
|
|
4
4
|
* body that does not decode in the coding it names.
|
|
5
5
|
*/
|
|
6
|
-
type RequestBodyRead = {
|
|
6
|
+
export type RequestBodyRead = {
|
|
7
7
|
readonly kind: "read";
|
|
8
8
|
readonly content: Uint8Array;
|
|
9
9
|
} | {
|
|
@@ -32,4 +32,3 @@ export declare function readRawBody(request: Request, cap: number): Promise<{
|
|
|
32
32
|
readonly kind: "read";
|
|
33
33
|
readonly content: Uint8Array;
|
|
34
34
|
}>;
|
|
35
|
-
export {};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type AvExecutionOptions, type AvFramework, type AvOperation, type AvState, type AvTransactionResults } from "@aventara/core";
|
|
2
|
-
import type { AccessClient } from "./host-bootstrap.validator.js";
|
|
3
2
|
/**
|
|
4
3
|
* `avClient.transaction`: runs `avClient.tx` handles as one transaction, with
|
|
5
4
|
* this request's state. Client-layer handles only: an `avApp.appTx` handle in
|
|
@@ -13,7 +12,7 @@ export type AvNextClientTransaction = <const Ops extends readonly AvOperation<un
|
|
|
13
12
|
*/
|
|
14
13
|
export type AvNextAppTransaction = <const Ops extends readonly AvOperation<unknown, "application">[]>(operations: Ops, options?: AvExecutionOptions) => Promise<AvTransactionResults<Ops>>;
|
|
15
14
|
/** Where the members find their Framework: built on first use, once. */
|
|
16
|
-
type FrameworkSource = {
|
|
15
|
+
export type FrameworkSource = {
|
|
17
16
|
/** The Framework, built on the first call; a construction error is kept. */
|
|
18
17
|
readonly built: () => Promise<AvFramework>;
|
|
19
18
|
/** The Framework when it is already built, else `undefined`. */
|
|
@@ -33,8 +32,7 @@ export declare function refuseMemberClashes(framework: AvFramework): void;
|
|
|
33
32
|
* request's state from `requestState` (a `state` the call names wins),
|
|
34
33
|
* `avApp`'s as given.
|
|
35
34
|
*/
|
|
36
|
-
export declare function createServerOperations(source: FrameworkSource, requestState: () => Promise<AvState | undefined
|
|
35
|
+
export declare function createServerOperations(source: FrameworkSource, requestState: () => Promise<AvState | undefined>): {
|
|
37
36
|
readonly avClient: object;
|
|
38
37
|
readonly avApp: object;
|
|
39
38
|
};
|
|
40
|
-
export {};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { AvConstructionError,
|
|
2
|
-
import { ACCESS_VERB_NAMES, ACCESS_VERBS, providerVerb, } from "@aventara/core/internal";
|
|
1
|
+
import { AvConstructionError, } from "@aventara/core";
|
|
3
2
|
import { io } from "next/cache.js";
|
|
4
|
-
import { headers } from "next/headers.js";
|
|
5
3
|
function ioBeforeCall() {
|
|
6
4
|
try {
|
|
7
5
|
return io();
|
|
@@ -11,7 +9,7 @@ function ioBeforeCall() {
|
|
|
11
9
|
}
|
|
12
10
|
}
|
|
13
11
|
const handlesBeforeBuild = (member) => `Aventara: \`${member}\` builds its handles on the Framework, which is built on first use; \`await ready()\` before building them.`;
|
|
14
|
-
const CLIENT_MEMBERS = ["tx", "transaction"
|
|
12
|
+
const CLIENT_MEMBERS = ["tx", "transaction"];
|
|
15
13
|
const APPLICATION_MEMBERS = ["appTx", "transaction"];
|
|
16
14
|
export function refuseMemberClashes(framework) {
|
|
17
15
|
const clashes = [
|
|
@@ -31,7 +29,7 @@ function clashesOf(tree, resources, members) {
|
|
|
31
29
|
.filter((member) => Object.hasOwn(resources, member))
|
|
32
30
|
.map((member) => ({ tree, member }));
|
|
33
31
|
}
|
|
34
|
-
export function createServerOperations(source, requestState
|
|
32
|
+
export function createServerOperations(source, requestState) {
|
|
35
33
|
const withRequestState = async (options) => {
|
|
36
34
|
if (options?.state !== undefined) {
|
|
37
35
|
return options;
|
|
@@ -42,12 +40,10 @@ export function createServerOperations(source, requestState, access) {
|
|
|
42
40
|
const asGiven = async (options) => options;
|
|
43
41
|
const clientTransaction = transactionOf(source, withRequestState);
|
|
44
42
|
const applicationTransaction = transactionOf(source, asGiven);
|
|
45
|
-
const verbs = accessOf(source, withRequestState, access);
|
|
46
43
|
return {
|
|
47
44
|
avClient: operationTree("avClient", source, (framework) => framework.serverClient, withRequestState, {
|
|
48
45
|
tx: () => builders(source, "avClient.tx").serverClientTx,
|
|
49
46
|
transaction: () => clientTransaction,
|
|
50
|
-
$access: () => verbs,
|
|
51
47
|
}),
|
|
52
48
|
avApp: operationTree("avApp", source, (framework) => framework.application, asGiven, {
|
|
53
49
|
appTx: () => builders(source, "avApp.appTx").appTx,
|
|
@@ -55,61 +51,6 @@ export function createServerOperations(source, requestState, access) {
|
|
|
55
51
|
}),
|
|
56
52
|
};
|
|
57
53
|
}
|
|
58
|
-
const ACCESS_VERB_OPTIONS = [
|
|
59
|
-
["can", 1],
|
|
60
|
-
...ACCESS_VERB_NAMES.map((name) => [name, ACCESS_VERBS[name].args ? 1 : 0]),
|
|
61
|
-
];
|
|
62
|
-
function servedVerbs(access) {
|
|
63
|
-
if (access === undefined) {
|
|
64
|
-
return new Set();
|
|
65
|
-
}
|
|
66
|
-
const verbs = access.client?.verbs ?? {};
|
|
67
|
-
return new Set([
|
|
68
|
-
"can",
|
|
69
|
-
...ACCESS_VERB_NAMES.filter((name) => providerVerb(verbs, name) !== undefined),
|
|
70
|
-
]);
|
|
71
|
-
}
|
|
72
|
-
function unservedVerb(name, access) {
|
|
73
|
-
const why = access === undefined
|
|
74
|
-
? `avClient.$access.${name}: this Framework has no \`access\`, so avClient has no $access.`
|
|
75
|
-
: `avClient.$access.${name}: the access provider offers no \`${name}\` (its \`client\` states no such verb).`;
|
|
76
|
-
return async () => {
|
|
77
|
-
throw new AvProtocolError("A2002", why);
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
function accessOf(source, fill, access) {
|
|
81
|
-
const served = servedVerbs(access);
|
|
82
|
-
const verbs = {};
|
|
83
|
-
const unserved = new Map();
|
|
84
|
-
for (const [name, at] of ACCESS_VERB_OPTIONS) {
|
|
85
|
-
if (!served.has(name)) {
|
|
86
|
-
unserved.set(name, unservedVerb(name, access));
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
verbs[name] = async (...args) => {
|
|
90
|
-
await ioBeforeCall();
|
|
91
|
-
const framework = await source.built();
|
|
92
|
-
const call = framework.serverClient.$access?.[name];
|
|
93
|
-
if (call === undefined) {
|
|
94
|
-
return unservedVerb(name, access)();
|
|
95
|
-
}
|
|
96
|
-
const given = args[at];
|
|
97
|
-
const called = args.slice(0, at);
|
|
98
|
-
called[at] =
|
|
99
|
-
name === "can"
|
|
100
|
-
? await fill(given)
|
|
101
|
-
: given?.headers !== undefined
|
|
102
|
-
? given
|
|
103
|
-
: { ...given, headers: await headers() };
|
|
104
|
-
return call(...called);
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
return new Proxy(Object.freeze(verbs), {
|
|
108
|
-
get: (target, key, receiver) => typeof key === "string" && unserved.has(key)
|
|
109
|
-
? unserved.get(key)
|
|
110
|
-
: Reflect.get(target, key, receiver),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
54
|
function builders(source, member) {
|
|
114
55
|
const framework = source.current();
|
|
115
56
|
if (framework === undefined) {
|
|
@@ -7,6 +7,8 @@ export type FrameworkSlot = {
|
|
|
7
7
|
/** The Framework once built. */
|
|
8
8
|
built: AvFramework | undefined;
|
|
9
9
|
};
|
|
10
|
+
/** The `globalThis` key the build for `api` is held under. */
|
|
11
|
+
export declare const frameworkKey: (api: string) => symbol;
|
|
10
12
|
/**
|
|
11
13
|
* The slot for `api` in this process: the one a copy of the same module
|
|
12
14
|
* already holds (the same `fingerprint`), else a new one. A different
|
|
@@ -14,12 +16,10 @@ export type FrameworkSlot = {
|
|
|
14
16
|
*
|
|
15
17
|
* @throws Error when another configuration holds `api` in production.
|
|
16
18
|
*/
|
|
17
|
-
export declare function frameworkSlot(api: string, fingerprint: string
|
|
19
|
+
export declare function frameworkSlot(api: string, fingerprint: string): FrameworkSlot;
|
|
18
20
|
/**
|
|
19
21
|
* The options' fingerprint: their plain data in a stable order; a function or
|
|
20
22
|
* any other object than plain data and arrays counts only as present, since
|
|
21
|
-
* bundlers spell them differently in each layer of one app.
|
|
22
|
-
* `"source"`, a function counts by its source text instead: what one layer
|
|
23
|
-
* compares with its own last call.
|
|
23
|
+
* bundlers spell them differently in each layer of one app.
|
|
24
24
|
*/
|
|
25
|
-
export declare function optionsFingerprint(options: unknown
|
|
25
|
+
export declare function optionsFingerprint(options: unknown): string;
|
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
const KEY_PREFIX = "aventara.next.framework:";
|
|
2
2
|
const environment = () => globalThis.process?.env;
|
|
3
|
-
const
|
|
4
|
-
const frameworkKey = (api) => Symbol.for(`${KEY_PREFIX}${api}`);
|
|
3
|
+
export const frameworkKey = (api) => Symbol.for(`${KEY_PREFIX}${api}`);
|
|
5
4
|
function claimed(api) {
|
|
6
5
|
return `Aventara: another configuration already serves the \`api\` path ${api === "" ? "/" : api} in this process; give each Aventara() its own \`api\` path.`;
|
|
7
6
|
}
|
|
8
|
-
export function frameworkSlot(api, fingerprint
|
|
7
|
+
export function frameworkSlot(api, fingerprint) {
|
|
9
8
|
const key = frameworkKey(api);
|
|
10
9
|
const global = globalThis;
|
|
11
10
|
const held = global[key];
|
|
12
|
-
const production = environment()?.NODE_ENV === "production";
|
|
13
|
-
const last = lastCalls.get(api);
|
|
14
11
|
if (held !== undefined) {
|
|
15
12
|
if (held.fingerprint === fingerprint) {
|
|
16
|
-
|
|
17
|
-
if (!edited) {
|
|
18
|
-
lastCalls.set(api, { source, slot: held });
|
|
19
|
-
return held;
|
|
20
|
-
}
|
|
13
|
+
return held;
|
|
21
14
|
}
|
|
22
|
-
|
|
15
|
+
if (environment()?.NODE_ENV === "production") {
|
|
23
16
|
throw new Error(claimed(api));
|
|
24
17
|
}
|
|
25
18
|
}
|
|
@@ -29,17 +22,14 @@ export function frameworkSlot(api, fingerprint, source) {
|
|
|
29
22
|
built: undefined,
|
|
30
23
|
};
|
|
31
24
|
global[key] = slot;
|
|
32
|
-
lastCalls.set(api, { source, slot });
|
|
33
25
|
return slot;
|
|
34
26
|
}
|
|
35
|
-
export function optionsFingerprint(options
|
|
27
|
+
export function optionsFingerprint(options) {
|
|
36
28
|
const seen = new Set();
|
|
37
29
|
const walk = (value) => {
|
|
38
30
|
switch (typeof value) {
|
|
39
31
|
case "function":
|
|
40
|
-
return
|
|
41
|
-
? `fn${JSON.stringify(Function.prototype.toString.call(value))}`
|
|
42
|
-
: "fn";
|
|
32
|
+
return "fn";
|
|
43
33
|
case "bigint":
|
|
44
34
|
return `${value}n`;
|
|
45
35
|
case "object":
|
|
@@ -21,8 +21,5 @@ export type UniversalPath = (url: string, init: UniversalRequest) => Promise<Uni
|
|
|
21
21
|
* or by the protocol's absence answer.
|
|
22
22
|
*/
|
|
23
23
|
export declare function universalPath(host: () => Promise<ProtocolHost>, state: StateSeed | undefined): UniversalPath;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
* and `access`, `avClient.$access`, as its `$access` verbs on the server.
|
|
27
|
-
*/
|
|
28
|
-
export declare function registerUniversalPath(path: UniversalPath, access?: object): void;
|
|
24
|
+
/** Registers `path` as this server's in-process path for `@/aventara/universal`. */
|
|
25
|
+
export declare function registerUniversalPath(path: UniversalPath): void;
|
|
@@ -26,11 +26,6 @@ export function universalPath(host, state) {
|
|
|
26
26
|
return { status: response.status, text: async () => response.body };
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const holder = globalThis;
|
|
32
|
-
holder[UNIVERSAL_PATH_KEY] = path;
|
|
33
|
-
if (access !== undefined) {
|
|
34
|
-
holder[UNIVERSAL_ACCESS_KEY] = access;
|
|
35
|
-
}
|
|
29
|
+
export function registerUniversalPath(path) {
|
|
30
|
+
globalThis[UNIVERSAL_PATH_KEY] = path;
|
|
36
31
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aventara/next",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.9",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "Next.js App Router host integration for Aventara.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": "^22.
|
|
8
|
+
"node": "^22.18.0 || >=24.2.0"
|
|
9
9
|
},
|
|
10
10
|
"main": "./dist/index.js",
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
"CHANGELOG.md"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@aventara/core": "1.0.0-rc.
|
|
25
|
+
"@aventara/core": "1.0.0-rc.9",
|
|
26
26
|
"server-only": "0.0.1"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@aventara/mcp": "1.0.0-rc.
|
|
29
|
+
"@aventara/mcp": "1.0.0-rc.9",
|
|
30
30
|
"next": ">=16.4 <17",
|
|
31
31
|
"react": "^19.0.0"
|
|
32
32
|
},
|
|
@@ -53,11 +53,11 @@
|
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@aventara/build": "1.0.0-rc.
|
|
57
|
-
"@aventara/client": "1.0.0-rc.
|
|
58
|
-
"@aventara/mcp": "1.0.0-rc.
|
|
59
|
-
"@aventara/testing": "1.0.0-rc.
|
|
60
|
-
"@modelcontextprotocol/sdk": "1.
|
|
56
|
+
"@aventara/build": "1.0.0-rc.9",
|
|
57
|
+
"@aventara/client": "1.0.0-rc.9",
|
|
58
|
+
"@aventara/mcp": "1.0.0-rc.9",
|
|
59
|
+
"@aventara/testing": "1.0.0-rc.9",
|
|
60
|
+
"@modelcontextprotocol/sdk": "1.30.1",
|
|
61
61
|
"@tanstack/react-query": "5.104.2",
|
|
62
62
|
"@types/react": "19.3.0",
|
|
63
63
|
"jsdom": "30.1.2",
|