evolit 0.1.7 → 0.2.0
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/README.md +15 -3
- package/package.json +2 -2
- package/src/navigation-server.js +0 -1
- package/src/render.js +14 -8
- package/src/request-context-browser.js +1 -0
- package/src/request-context.js +53 -1
- package/src/server-api.js +7 -0
package/README.md
CHANGED
|
@@ -20,8 +20,8 @@ It focuses on the core contract that matters first:
|
|
|
20
20
|
- route modules live in `app/**/page.*`
|
|
21
21
|
- layout modules live in `app/**/layout.*`
|
|
22
22
|
- page and layout modules export a default async function
|
|
23
|
-
- pages receive `{ params, searchParams, request }`
|
|
24
|
-
- layouts receive `{ children, params, searchParams, request }`
|
|
23
|
+
- pages receive `{ params, searchParams, navigationContext, request }`
|
|
24
|
+
- layouts receive `{ children, params, searchParams, navigationContext, request }`
|
|
25
25
|
- SSR document rendering is delegated to `@litsx/ssr`
|
|
26
26
|
|
|
27
27
|
Supported authored module extensions:
|
|
@@ -266,20 +266,24 @@ Server pages and layouts can access the active Web Request context through `evol
|
|
|
266
266
|
```js
|
|
267
267
|
import {
|
|
268
268
|
cookies,
|
|
269
|
+
getRouteState,
|
|
269
270
|
headers,
|
|
270
271
|
notFound,
|
|
272
|
+
permanentRedirect,
|
|
271
273
|
redirect,
|
|
272
274
|
requestUrl,
|
|
273
275
|
responseHeaders,
|
|
274
276
|
} from "evolit/server";
|
|
275
277
|
|
|
276
278
|
export default async function AccountPage() {
|
|
279
|
+
const { params, searchParams, navigationContext } = getRouteState();
|
|
280
|
+
|
|
277
281
|
if (!cookies().has("session")) {
|
|
278
282
|
redirect("/sign-in");
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
responseHeaders().set("x-account-page", "1");
|
|
282
|
-
return `<p>${headers().get("user-agent")} ${requestUrl().pathname}</p>`;
|
|
286
|
+
return `<p>${headers().get("user-agent")} ${requestUrl().pathname} ${params.account ?? ""}</p>`;
|
|
283
287
|
}
|
|
284
288
|
```
|
|
285
289
|
|
|
@@ -288,6 +292,14 @@ headers. `redirect()` and `permanentRedirect()` end rendering with `307` and `30
|
|
|
288
292
|
`notFound()` renders a `404`. Reading headers, cookies, or the request URL makes the completed
|
|
289
293
|
render dynamic, so it is not stored by the route response cache.
|
|
290
294
|
|
|
295
|
+
`getRouteState()` is the request-scoped server counterpart for reading the active route from nested
|
|
296
|
+
server components and helpers that do not receive route props directly. It returns the current
|
|
297
|
+
`{ url, params, searchParams, navigationContext }` as a read-only snapshot. It deliberately has no
|
|
298
|
+
`push`, `replace`, `refresh`, pending state, or history mutation methods: those belong to the browser
|
|
299
|
+
`useNavigation()` API. Redirects and `notFound()` remain separate server control-flow functions.
|
|
300
|
+
Reading `getRouteState().url` has the same dynamic-rendering semantics as `requestUrl()`; reading
|
|
301
|
+
`params` and `searchParams` participates in the normal segment-cache key tracking.
|
|
302
|
+
|
|
291
303
|
## Extensions
|
|
292
304
|
|
|
293
305
|
Optional integrations are configured explicitly in `evolit.config.js`. Core only coordinates their
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A convention-driven application framework for LitSX and web components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "yarn@4.10.3",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@jridgewell/remapping": "^2.3.5",
|
|
62
|
-
"@litsx/compiler": "1.0.0-next.
|
|
62
|
+
"@litsx/compiler": "1.0.0-next.13",
|
|
63
63
|
"@litsx/core": "1.0.0-next.8",
|
|
64
64
|
"@litsx/ssr": "1.0.0-next.5",
|
|
65
65
|
"@rollup/plugin-commonjs": "^29.0.0",
|
package/src/navigation-server.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export function getNavigation() { throw new Error("Navigation is only available in a browser context."); }
|
|
2
1
|
export { createHref } from "./navigation-url.js";
|
|
3
2
|
export function useNavigation() { throw new Error("useNavigation() is only available in a browser component."); }
|
|
4
3
|
export function useParams() { throw new Error("useParams() is only available in a browser component."); }
|
package/src/render.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getRequestContextResponse,
|
|
15
15
|
isEvolitHttpSignal,
|
|
16
16
|
runWithRequestContext,
|
|
17
|
+
runWithRouteState,
|
|
17
18
|
} from "./request-context.js";
|
|
18
19
|
import { createDevelopmentEventReporter } from "./development-events.js";
|
|
19
20
|
import {
|
|
@@ -486,16 +487,21 @@ async function renderSegmentedComponentTree(
|
|
|
486
487
|
const trackedParams = createTrackedRouteValues(requestContext.params);
|
|
487
488
|
const trackedSearchParams = createTrackedRouteValues(requestContext.searchParams);
|
|
488
489
|
const didUseDynamicRequestData = requestContext.didUseDynamicRequestData;
|
|
489
|
-
|
|
490
|
+
layoutResult = await runWithRouteState(requestContext, {
|
|
490
491
|
params: trackedParams.value,
|
|
491
492
|
searchParams: trackedSearchParams.value,
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
493
|
+
}, async () => {
|
|
494
|
+
const layoutValue = await layoutComponents[index]({
|
|
495
|
+
params: trackedParams.value,
|
|
496
|
+
searchParams: trackedSearchParams.value,
|
|
497
|
+
navigationContext: requestContext.navigationContext,
|
|
498
|
+
request,
|
|
499
|
+
children: withForwardedChildRef(html`${unsafeHTML(childrenMarker)}`, childRef),
|
|
500
|
+
}, incomingRef);
|
|
501
|
+
return renderToString(wrapRouteSegment(segment, layoutValue), {
|
|
502
|
+
assetResolver: options.assetResolver,
|
|
503
|
+
context: { idPrefix },
|
|
504
|
+
});
|
|
499
505
|
});
|
|
500
506
|
layoutResult.segmentModulePath = segment.modulePath;
|
|
501
507
|
profile = {
|
|
@@ -10,3 +10,4 @@ export function redirect() { return throwServerOnlyApi("redirect"); }
|
|
|
10
10
|
export function requestUrl() { return throwServerOnlyApi("requestUrl"); }
|
|
11
11
|
export function responseHeaders() { return throwServerOnlyApi("responseHeaders"); }
|
|
12
12
|
export function getRequestContext() { return throwServerOnlyApi("getRequestContext"); }
|
|
13
|
+
export function getRouteState() { return throwServerOnlyApi("getRouteState"); }
|
package/src/request-context.js
CHANGED
|
@@ -2,9 +2,11 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import { readNavigationContext } from "./navigation-context.js";
|
|
3
3
|
|
|
4
4
|
const REQUEST_CONTEXT_STORAGE = Symbol.for("evolit.request-context.storage");
|
|
5
|
+
const ROUTE_STATE_STORAGE = Symbol.for("evolit.route-state.storage");
|
|
5
6
|
const HTTP_SIGNAL_CLASS = Symbol.for("evolit.request-context.http-signal");
|
|
6
7
|
|
|
7
8
|
const requestContextStorage = globalThis[REQUEST_CONTEXT_STORAGE] ??= new AsyncLocalStorage();
|
|
9
|
+
const routeStateStorage = globalThis[ROUTE_STATE_STORAGE] ??= new AsyncLocalStorage();
|
|
8
10
|
const EvolitHttpSignal = globalThis[HTTP_SIGNAL_CLASS] ??= class EvolitHttpSignal extends Error {
|
|
9
11
|
constructor(type, options = {}) {
|
|
10
12
|
super(type);
|
|
@@ -111,11 +113,14 @@ export function createRequestContext({
|
|
|
111
113
|
extensionValues = {},
|
|
112
114
|
didUseDynamicRequestData = false,
|
|
113
115
|
}) {
|
|
116
|
+
const routeNavigationContext = navigationContext == null
|
|
117
|
+
? null
|
|
118
|
+
: Object.freeze({ ...navigationContext });
|
|
114
119
|
const context = {
|
|
115
120
|
request,
|
|
116
121
|
params: Object.freeze({ ...params }),
|
|
117
122
|
searchParams: Object.freeze({ ...searchParams }),
|
|
118
|
-
navigationContext,
|
|
123
|
+
navigationContext: routeNavigationContext,
|
|
119
124
|
responseHeaders: new Headers(),
|
|
120
125
|
responseCookies: [],
|
|
121
126
|
didUseDynamicRequestData: didUseDynamicRequestData === true,
|
|
@@ -130,6 +135,53 @@ export function runWithRequestContext(context, callback) {
|
|
|
130
135
|
return requestContextStorage.run(context, callback);
|
|
131
136
|
}
|
|
132
137
|
|
|
138
|
+
export function runWithRouteState(context, routeState, callback) {
|
|
139
|
+
return routeStateStorage.run({ context, ...routeState }, callback);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Returns a read-only snapshot of the active SSR route. Unlike the browser
|
|
144
|
+
* navigation controller, this state has no history or mutation methods.
|
|
145
|
+
* Reading `url` follows the same dynamic-rendering semantics as requestUrl().
|
|
146
|
+
*
|
|
147
|
+
* @returns {{
|
|
148
|
+
* url: URL,
|
|
149
|
+
* params: Readonly<Record<string, string | string[] | undefined>>,
|
|
150
|
+
* searchParams: Readonly<Record<string, string | string[] | undefined>>,
|
|
151
|
+
* navigationContext: Readonly<Record<string, unknown>> | null,
|
|
152
|
+
* }}
|
|
153
|
+
*/
|
|
154
|
+
export function getRouteState() {
|
|
155
|
+
const context = getActiveContext();
|
|
156
|
+
const trackedState = routeStateStorage.getStore();
|
|
157
|
+
const routeState = trackedState?.context === context ? trackedState : context;
|
|
158
|
+
const state = {};
|
|
159
|
+
|
|
160
|
+
Object.defineProperties(state, {
|
|
161
|
+
url: {
|
|
162
|
+
enumerable: true,
|
|
163
|
+
get() {
|
|
164
|
+
context.didUseDynamicRequestData = true;
|
|
165
|
+
return new URL(context.request.url);
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
params: {
|
|
169
|
+
enumerable: true,
|
|
170
|
+
value: routeState.params,
|
|
171
|
+
},
|
|
172
|
+
searchParams: {
|
|
173
|
+
enumerable: true,
|
|
174
|
+
value: routeState.searchParams,
|
|
175
|
+
},
|
|
176
|
+
navigationContext: {
|
|
177
|
+
enumerable: true,
|
|
178
|
+
value: context.navigationContext,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return Object.freeze(state);
|
|
183
|
+
}
|
|
184
|
+
|
|
133
185
|
/**
|
|
134
186
|
* Returns serializable values supplied by configured server extensions for
|
|
135
187
|
* the active request. The object is isolated through AsyncLocalStorage and is
|
package/src/server-api.js
CHANGED
|
@@ -52,3 +52,10 @@ export { responseHeaders } from "./request-context.js";
|
|
|
52
52
|
|
|
53
53
|
/** Returns the active request's extension-provided serializable values. */
|
|
54
54
|
export { getRequestContext } from "./request-context.js";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns a read-only snapshot of the active SSR route. This is the server
|
|
58
|
+
* counterpart for reading navigation state; redirects remain separate
|
|
59
|
+
* control-flow functions and browser history methods are intentionally absent.
|
|
60
|
+
*/
|
|
61
|
+
export { getRouteState } from "./request-context.js";
|