@kasperrt/smolter 0.3.1 → 0.3.3
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/.generated/client/deployment-check.ts +70 -0
- package/.generated/client/deployment.ts +24 -59
- package/.generated/client/index.ts +8 -2
- package/.generated/core/responses.ts +8 -3
- package/.generated/core/router.ts +32 -0
- package/.generated/core/types.ts +2 -0
- package/.generated/preact/client.tsx +48 -6
- package/.generated/preact/document.tsx +4 -2
- package/.generated/preact/lazy-route.ts +2 -0
- package/.generated/preact/root-context.ts +2 -0
- package/.generated/preact/route-types.ts +1 -0
- package/.generated/preact/router.tsx +9 -3
- package/.generated/server/application.tsx +83 -19
- package/.generated/server/handler.ts +8 -2
- package/.generated/server/http.ts +24 -2
- package/.generated/server/index.ts +17 -5
- package/.generated/server/preact.ts +1 -0
- package/.generated/server/private-response.ts +11 -0
- package/.generated/server/stream-document.ts +121 -0
- package/.generated/vite/application/lazy-files.ts +8 -2
- package/.generated/vite/application/vite.ts +9 -3
- package/.generated/vite/index.ts +4 -2
- package/.generated/vite/transport.ts +4 -1
- package/CHANGELOG.md +15 -0
- package/README.md +29 -11
- package/dist/client/deployment-check.d.ts +6 -0
- package/dist/client/deployment-check.js +51 -0
- package/dist/client/deployment.d.ts +2 -0
- package/dist/client/deployment.js +12 -40
- package/dist/client/index.d.ts +3 -1
- package/dist/client/index.js +3 -3
- package/dist/core/responses.d.ts +1 -1
- package/dist/core/responses.js +3 -3
- package/dist/core/router.js +16 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/preact/client.d.ts +6 -1
- package/dist/preact/client.js +27 -8
- package/dist/preact/document.js +4 -2
- package/dist/preact/lazy-route.d.ts +1 -0
- package/dist/preact/lazy-route.js +2 -1
- package/dist/preact/root-context.d.ts +2 -0
- package/dist/preact/route-types.d.ts +1 -0
- package/dist/preact/router.d.ts +1 -1
- package/dist/preact/router.js +3 -3
- package/dist/server/application.d.ts +4 -1
- package/dist/server/application.js +47 -13
- package/dist/server/handler.d.ts +3 -1
- package/dist/server/handler.js +5 -3
- package/dist/server/http.d.ts +5 -1
- package/dist/server/http.js +10 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +12 -2
- package/dist/server/preact.d.ts +1 -1
- package/dist/server/private-response.d.ts +2 -0
- package/dist/server/private-response.js +13 -0
- package/dist/server/stream-document.d.ts +9 -0
- package/dist/server/stream-document.js +78 -0
- package/dist/vite/application/lazy-files.d.ts +1 -1
- package/dist/vite/application/lazy-files.js +5 -2
- package/dist/vite/application/vite.d.ts +2 -1
- package/dist/vite/application/vite.js +7 -4
- package/dist/vite/index.d.ts +2 -1
- package/dist/vite/index.js +6 -3
- package/dist/vite/transport.d.ts +1 -1
- package/dist/vite/transport.js +3 -2
- package/jsr.json +1 -1
- package/package.json +2 -2
package/dist/core/responses.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ export interface DataResponse<T> {
|
|
|
11
11
|
export declare function data<T>(value: T, init?: ResponseInit): DataResponse<T>;
|
|
12
12
|
export declare function redirect(location: string, init?: number | ResponseInit): Response;
|
|
13
13
|
export declare function isRouteErrorResponse(value: unknown): value is RouteError;
|
|
14
|
-
export declare function normalizeResult(value: unknown): Promise<unknown>;
|
|
14
|
+
export declare function normalizeResult(value: unknown, resolveDeferred?: boolean): Promise<unknown>;
|
|
15
15
|
export declare function resolveData(value: unknown): Promise<unknown>;
|
package/dist/core/responses.js
CHANGED
|
@@ -19,7 +19,7 @@ function redirect(location, init = 302) {
|
|
|
19
19
|
function isRouteErrorResponse(value) {
|
|
20
20
|
return value instanceof Error && "status" in value;
|
|
21
21
|
}
|
|
22
|
-
async function normalizeResult(value) {
|
|
22
|
+
async function normalizeResult(value, resolveDeferred = true) {
|
|
23
23
|
if (value && typeof value === "object" && "__routeError" in value) {
|
|
24
24
|
const error = value.__routeError;
|
|
25
25
|
return Object.assign(Error(error.message), error);
|
|
@@ -32,8 +32,8 @@ async function normalizeResult(value) {
|
|
|
32
32
|
});
|
|
33
33
|
return value.json();
|
|
34
34
|
}
|
|
35
|
-
if (value && typeof value === "object" && "__routeData" in value) return resolveData(value.value);
|
|
36
|
-
return resolveData(value);
|
|
35
|
+
if (value && typeof value === "object" && "__routeData" in value) return resolveDeferred ? resolveData(value.value) : value.value;
|
|
36
|
+
return resolveDeferred ? resolveData(value) : value;
|
|
37
37
|
}
|
|
38
38
|
async function resolveData(value) {
|
|
39
39
|
const resolved = await value;
|
package/dist/core/router.js
CHANGED
|
@@ -71,6 +71,7 @@ function createDataRouter(routes, options = {}) {
|
|
|
71
71
|
const fn = match.route[body ? "clientAction" : "clientLoader"] || match.route[body ? "action" : "loader"];
|
|
72
72
|
if (!fn) return body && Error("No action");
|
|
73
73
|
return fn({
|
|
74
|
+
reason: body ? "action" : navigation !== void 0 ? "navigation" : local ? "fetcher" : previous.loaded ? "revalidation" : "initial",
|
|
74
75
|
request: new Request(url, {
|
|
75
76
|
signal,
|
|
76
77
|
method: body && method,
|
|
@@ -84,6 +85,21 @@ function createDataRouter(routes, options = {}) {
|
|
|
84
85
|
const redirectTo = result.data instanceof Response && result.data.headers.get("Location");
|
|
85
86
|
if (redirectTo && redirects) {
|
|
86
87
|
await main.load(new URL(redirectTo, url).href, void 0, { replace: true }, true, redirects - 1);
|
|
88
|
+
if (body) {
|
|
89
|
+
const destination = new URL(main.getSnapshot().url);
|
|
90
|
+
const args = {
|
|
91
|
+
url: destination,
|
|
92
|
+
currentUrl: destination,
|
|
93
|
+
nextUrl: destination,
|
|
94
|
+
defaultShouldRevalidate: true,
|
|
95
|
+
reason: "action",
|
|
96
|
+
formMethod: method,
|
|
97
|
+
formAction: url.href,
|
|
98
|
+
formData: body,
|
|
99
|
+
actionResult: result.data
|
|
100
|
+
};
|
|
101
|
+
await Promise.all([...revalidators].filter((task) => task !== main).map((task) => task.revalidate(args)));
|
|
102
|
+
}
|
|
87
103
|
if (!signal.aborted) {
|
|
88
104
|
submission = void 0;
|
|
89
105
|
emit("idle");
|
package/dist/core/types.d.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export type Params = Record<string, string>;
|
|
7
7
|
/** Request, path parameters, and cancellation signal passed to a data handler. */
|
|
8
8
|
export interface DataArgs {
|
|
9
|
+
/** Why this handler ran; independent of when browser history is committed. */
|
|
10
|
+
reason?: "initial" | "navigation" | "revalidation" | "fetcher" | "action";
|
|
9
11
|
request: Request;
|
|
10
12
|
params: Params;
|
|
11
13
|
signal: AbortSignal;
|
package/dist/preact/client.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/** Browser mounting and hydration for Preact applications. @module */
|
|
2
2
|
/** @jsxImportSource preact */
|
|
3
|
+
import { type DeploymentOptions } from "../client/index.ts";
|
|
3
4
|
import { type Router } from "../core/index.ts";
|
|
4
5
|
import type { AppRoute, RootModule } from "./route-types.ts";
|
|
5
6
|
export * from "../client/index.ts";
|
|
6
7
|
export { configureApplication } from "../core/environment.ts";
|
|
7
|
-
export
|
|
8
|
+
export interface MountApplicationOptions {
|
|
9
|
+
hash?: boolean;
|
|
10
|
+
deployment?: DeploymentOptions | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function mountApplication(routes: AppRoute[], rootModule?: RootModule, options?: boolean | MountApplicationOptions): Promise<Router>;
|
package/dist/preact/client.js
CHANGED
|
@@ -6,7 +6,7 @@ import { connectNavigation } from "../core/navigation.js";
|
|
|
6
6
|
import { attachRootData } from "./root-data.js";
|
|
7
7
|
import { RouterApplication, prepareRoutes } from "./router.js";
|
|
8
8
|
import { deserialize } from "../client/deserialize.js";
|
|
9
|
-
import { watchDeployment } from "../client/deployment.js";
|
|
9
|
+
import { recoverDeployment, watchDeployment } from "../client/deployment.js";
|
|
10
10
|
import { serverRoute } from "../client/index.js";
|
|
11
11
|
import { flushSync } from "preact/compat";
|
|
12
12
|
import { jsx } from "preact/jsx-runtime";
|
|
@@ -14,20 +14,31 @@ import { hydrate, render } from "preact";
|
|
|
14
14
|
//#region .generated/preact/client.tsx
|
|
15
15
|
/** Browser mounting and hydration for Preact applications. @module */
|
|
16
16
|
/** @jsxImportSource preact */
|
|
17
|
-
async function mountApplication(routes, rootModule,
|
|
17
|
+
async function mountApplication(routes, rootModule, options = false) {
|
|
18
|
+
const { hash = false, deployment = null } = typeof options === "boolean" ? { hash: options } : options;
|
|
18
19
|
const initialURL = new URL(hash ? location.hash.slice(1) || "/" : location.href, location.origin);
|
|
19
|
-
await (matchRoutes(routes, initialURL.pathname)?.route)?.preload?.()
|
|
20
|
+
await (matchRoutes(routes, initialURL.pathname)?.route)?.preload?.().catch(async (error) => {
|
|
21
|
+
await recoverDeployment(deployment, initialURL);
|
|
22
|
+
throw error;
|
|
23
|
+
});
|
|
20
24
|
routes = prepareRoutes(routes);
|
|
21
25
|
const element = document.getElementById("smolter-data");
|
|
22
|
-
const hydration = element?.textContent ? JSON.parse(element.textContent)
|
|
26
|
+
const hydration = element?.textContent ? JSON.parse(element.textContent, (_key, value) => {
|
|
27
|
+
if (!value || typeof value !== "object" || !("__smolterRejected" in value)) return value;
|
|
28
|
+
const error = Object.assign(new Error(value.__smolterRejected.message), value.__smolterRejected);
|
|
29
|
+
const rejected = Promise.reject(error);
|
|
30
|
+
rejected.catch(() => {});
|
|
31
|
+
return rejected;
|
|
32
|
+
}) : void 0;
|
|
23
33
|
if (hydration?.snapshot.error) hydration.snapshot.error = Object.assign(new Error(hydration.snapshot.error.message), hydration.snapshot.error);
|
|
24
34
|
const router = createDataRouter(routes, {
|
|
25
35
|
browser: !hash,
|
|
26
36
|
url: hash ? new URL(location.hash.slice(1) || "/", location.origin).href : location.href,
|
|
27
37
|
...hydration && { hydrationData: hydration.snapshot }
|
|
28
38
|
});
|
|
29
|
-
|
|
39
|
+
watchDeployment(router, deployment);
|
|
30
40
|
if (hash) connectHashHistory(router);
|
|
41
|
+
retainRoute(router, flushSync);
|
|
31
42
|
connectNavigation(router);
|
|
32
43
|
const rootStore = attachRootData(router, hydration?.root, rootModule);
|
|
33
44
|
const props = {
|
|
@@ -35,7 +46,8 @@ async function mountApplication(routes, rootModule, hash = false) {
|
|
|
35
46
|
router,
|
|
36
47
|
root: hydration?.root,
|
|
37
48
|
rootModule,
|
|
38
|
-
rootStore
|
|
49
|
+
rootStore,
|
|
50
|
+
rootError: hydration?.rootError
|
|
39
51
|
};
|
|
40
52
|
if (hydration) {
|
|
41
53
|
hydrate(/* @__PURE__ */ jsx(RouterApplication, {
|
|
@@ -49,20 +61,27 @@ async function mountApplication(routes, rootModule, hash = false) {
|
|
|
49
61
|
return router;
|
|
50
62
|
}
|
|
51
63
|
function connectHashHistory(router) {
|
|
64
|
+
const read = router.getSnapshot;
|
|
52
65
|
const navigate = router.navigate;
|
|
53
66
|
router.navigate = async (url, options) => {
|
|
54
67
|
const target = new URL(url, router.getSnapshot().url);
|
|
55
|
-
const hash = `#${target.pathname}${target.search}`;
|
|
68
|
+
const hash = `#${target.pathname}${target.search}${target.hash}`;
|
|
56
69
|
history[options?.replace ? "replaceState" : "pushState"](null, "", hash);
|
|
57
70
|
await navigate(url, options);
|
|
58
71
|
};
|
|
72
|
+
const unsubscribe = router.subscribe(() => {
|
|
73
|
+
const target = new URL(read().url);
|
|
74
|
+
const hash = `#${target.pathname}${target.search}${target.hash}`;
|
|
75
|
+
if (location.hash !== hash) history.replaceState(history.state, "", hash);
|
|
76
|
+
});
|
|
59
77
|
const onHashChange = () => void navigate(location.hash.slice(1) || "/");
|
|
60
78
|
window.addEventListener("hashchange", onHashChange);
|
|
61
79
|
const dispose = router.dispose;
|
|
62
80
|
router.dispose = () => {
|
|
81
|
+
unsubscribe();
|
|
63
82
|
window.removeEventListener("hashchange", onHashChange);
|
|
64
83
|
dispose();
|
|
65
84
|
};
|
|
66
85
|
}
|
|
67
86
|
//#endregion
|
|
68
|
-
export { configureApplication, deserialize, mountApplication, serverRoute, watchDeployment };
|
|
87
|
+
export { configureApplication, deserialize, mountApplication, recoverDeployment, serverRoute, watchDeployment };
|
package/dist/preact/document.js
CHANGED
|
@@ -44,8 +44,9 @@ function Links() {
|
|
|
44
44
|
return /* @__PURE__ */ jsx(Fragment$1, { children: rootModule?.links?.().map((props, index) => /* @__PURE__ */ jsx("link", { ...props }, index)) });
|
|
45
45
|
}
|
|
46
46
|
function Scripts({ nonce }) {
|
|
47
|
-
const { root, router, entry } = useApplication();
|
|
47
|
+
const { root, rootError, router, entry, streaming } = useApplication();
|
|
48
48
|
const [json] = useState(() => {
|
|
49
|
+
if (streaming) return "";
|
|
49
50
|
const current = router.getSnapshot();
|
|
50
51
|
const snapshot = {
|
|
51
52
|
...current,
|
|
@@ -57,7 +58,8 @@ function Scripts({ nonce }) {
|
|
|
57
58
|
};
|
|
58
59
|
return serializeData({
|
|
59
60
|
root,
|
|
60
|
-
snapshot
|
|
61
|
+
snapshot,
|
|
62
|
+
...rootError && { rootError }
|
|
61
63
|
});
|
|
62
64
|
});
|
|
63
65
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
@@ -16,7 +16,8 @@ function lazyRoute(options, importer) {
|
|
|
16
16
|
pending ??= importer().then((loaded) => {
|
|
17
17
|
module = serverRoute(options.path, loaded, {
|
|
18
18
|
loader: options.loader,
|
|
19
|
-
action: options.action
|
|
19
|
+
action: options.action,
|
|
20
|
+
dataEndpoint: options.dataEndpoint
|
|
20
21
|
});
|
|
21
22
|
}).catch((error) => {
|
|
22
23
|
pending = void 0;
|
package/dist/preact/router.d.ts
CHANGED
|
@@ -13,4 +13,4 @@ export interface ApplicationProps extends ApplicationContext {
|
|
|
13
13
|
rootStore?: ReturnType<typeof createRootStore>;
|
|
14
14
|
}
|
|
15
15
|
export declare function RouterApplication(props: ApplicationProps): import("preact").JSX.Element;
|
|
16
|
-
export declare function prepareRoutes(routes: AppRoute[]): AppRoute[];
|
|
16
|
+
export declare function prepareRoutes(routes: AppRoute[], preserveDeferred?: boolean): AppRoute[];
|
package/dist/preact/router.js
CHANGED
|
@@ -21,7 +21,7 @@ function RouterApplication(props) {
|
|
|
21
21
|
const root = useSyncExternalStore(rootStore.subscribe, rootStore.getSnapshot);
|
|
22
22
|
const snapshot = useSyncExternalStore(props.router.subscribe, props.router.getSnapshot);
|
|
23
23
|
const route = props.routes.find((item) => item.path === snapshot.path);
|
|
24
|
-
const Root = snapshot.error && !route?.ErrorBoundary ? props.rootModule?.ErrorBoundary ?? Outlet : props.rootModule?.default ?? Outlet;
|
|
24
|
+
const Root = snapshot.error && (props.rootError || !route?.ErrorBoundary) ? props.rootModule?.ErrorBoundary ?? Outlet : props.rootModule?.default ?? Outlet;
|
|
25
25
|
const Layout = props.rootModule?.Layout;
|
|
26
26
|
const children = /* @__PURE__ */ jsx(Root, {});
|
|
27
27
|
return /* @__PURE__ */ jsx(RouterProvider, {
|
|
@@ -35,7 +35,7 @@ function RouterApplication(props) {
|
|
|
35
35
|
})
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
-
function prepareRoutes(routes) {
|
|
38
|
+
function prepareRoutes(routes, preserveDeferred = false) {
|
|
39
39
|
return routes.map((route) => {
|
|
40
40
|
const wrapped = {
|
|
41
41
|
...route,
|
|
@@ -54,7 +54,7 @@ function prepareRoutes(routes) {
|
|
|
54
54
|
]) {
|
|
55
55
|
const handler = route[key];
|
|
56
56
|
if (!handler) continue;
|
|
57
|
-
wrapped[key] = (args) => Promise.resolve().then(() => handler(args)).then(normalizeResult, normalizeResult);
|
|
57
|
+
wrapped[key] = (args) => Promise.resolve().then(() => handler(args)).then((value) => normalizeResult(value, !preserveDeferred), (error) => normalizeResult(error, !preserveDeferred));
|
|
58
58
|
}
|
|
59
59
|
return wrapped;
|
|
60
60
|
});
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/** @jsxImportSource preact */
|
|
2
2
|
import type { AppRoute, RootModule } from "../preact/route-types.ts";
|
|
3
|
-
export
|
|
3
|
+
export interface RenderApplicationOptions {
|
|
4
|
+
nonce?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function renderApplication(request: Request, routes: AppRoute[], rootModule: RootModule, entry: string, options?: RenderApplicationOptions): Promise<Response>;
|
|
4
7
|
export declare function dispatchRoute(request: Request, routes: AppRoute[], document?: boolean): Promise<Response>;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import { safeWrapAsync } from "../core/errors.js";
|
|
1
2
|
import { matchRoutes } from "../core/match.js";
|
|
2
3
|
import { createDataRouter } from "../core/router.js";
|
|
3
4
|
import { normalizeResult, resolveData } from "../core/responses.js";
|
|
4
5
|
import { RouterApplication, prepareRoutes } from "../preact/router.js";
|
|
6
|
+
import { privateResponse } from "./private-response.js";
|
|
5
7
|
import { loadRouter } from "./index.js";
|
|
8
|
+
import { resolveHydration, streamDocument } from "./stream-document.js";
|
|
6
9
|
import { jsx } from "preact/jsx-runtime";
|
|
7
|
-
import {
|
|
10
|
+
import { renderToReadableStream } from "preact-render-to-string/stream";
|
|
8
11
|
//#region .generated/server/application.tsx
|
|
9
12
|
/** @jsxImportSource preact */
|
|
10
|
-
async function renderApplication(request, routes, rootModule, entry) {
|
|
13
|
+
async function renderApplication(request, routes, rootModule, entry, options = {}) {
|
|
11
14
|
routes = routes.map((route) => ({
|
|
12
15
|
...route,
|
|
13
16
|
path: route.routePath ?? route.path
|
|
@@ -21,16 +24,16 @@ async function renderApplication(request, routes, rootModule, entry) {
|
|
|
21
24
|
if (actionResponse.headers.has("Location") || !actionResponse.headers.get("Content-Type")?.includes("application/json")) return actionResponse;
|
|
22
25
|
actionData = await normalizeResult(await actionResponse.json());
|
|
23
26
|
}
|
|
24
|
-
const
|
|
27
|
+
const rootResult = safeWrapAsync(() => Promise.resolve().then(() => rootModule.loader?.({
|
|
25
28
|
request,
|
|
26
29
|
params: {},
|
|
27
30
|
signal: request.signal
|
|
28
|
-
}));
|
|
31
|
+
})).then((value) => normalizeResult(value, false), (error) => error instanceof Response ? normalizeResult(error, false) : error instanceof Error ? error : new Error(String(error), { cause: error })));
|
|
29
32
|
let redirectResponse;
|
|
30
33
|
const prepared = prepareRoutes(routes.map((route) => ({
|
|
31
34
|
...route,
|
|
32
35
|
ErrorBoundary: route.ErrorBoundary ?? rootModule.ErrorBoundary
|
|
33
|
-
}))).map((route) => {
|
|
36
|
+
})), true).map((route) => {
|
|
34
37
|
if (!route.loader) return route;
|
|
35
38
|
const loader = route.loader;
|
|
36
39
|
return {
|
|
@@ -42,41 +45,68 @@ async function renderApplication(request, routes, rootModule, entry) {
|
|
|
42
45
|
}
|
|
43
46
|
};
|
|
44
47
|
});
|
|
45
|
-
|
|
48
|
+
const [rootState, loadedRouter] = await Promise.all([rootResult, loadRouter(prepared, request)]);
|
|
49
|
+
let router = loadedRouter;
|
|
50
|
+
const rootError = rootState.error ?? (rootState.data instanceof Error ? rootState.data : void 0);
|
|
51
|
+
const root = rootError ? void 0 : rootState.data;
|
|
52
|
+
if (root instanceof Response && root.headers.has("Location")) {
|
|
53
|
+
router.dispose();
|
|
54
|
+
return root;
|
|
55
|
+
}
|
|
46
56
|
if (redirectResponse) {
|
|
47
57
|
router.dispose();
|
|
48
58
|
return redirectResponse;
|
|
49
59
|
}
|
|
50
|
-
if (actionResponse) {
|
|
60
|
+
if (actionResponse || rootError) {
|
|
51
61
|
const hydrationData = {
|
|
52
62
|
...router.getSnapshot(),
|
|
53
63
|
actionData: actionData instanceof Error ? void 0 : actionData,
|
|
54
|
-
...actionData instanceof Error && { error: actionData }
|
|
64
|
+
...actionData instanceof Error && { error: actionData },
|
|
65
|
+
...rootError && { error: rootError }
|
|
55
66
|
};
|
|
56
67
|
router.dispose();
|
|
57
68
|
router = createDataRouter(router.routes, { hydrationData });
|
|
58
69
|
}
|
|
59
70
|
const snapshot = router.getSnapshot();
|
|
60
|
-
const
|
|
71
|
+
const stream = renderToReadableStream(/* @__PURE__ */ jsx(RouterApplication, {
|
|
61
72
|
router,
|
|
62
73
|
routes,
|
|
63
74
|
root,
|
|
64
75
|
rootModule,
|
|
65
76
|
entry,
|
|
77
|
+
rootError: Boolean(rootError),
|
|
78
|
+
streaming: true,
|
|
66
79
|
document: true
|
|
67
|
-
}));
|
|
68
|
-
|
|
80
|
+
}), options);
|
|
81
|
+
const hydration = resolveHydration({
|
|
82
|
+
root,
|
|
83
|
+
snapshot: {
|
|
84
|
+
...snapshot,
|
|
85
|
+
error: snapshot.error && {
|
|
86
|
+
...snapshot.error,
|
|
87
|
+
name: snapshot.error.name,
|
|
88
|
+
message: snapshot.error.message
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
...rootError && { rootError: true }
|
|
92
|
+
});
|
|
93
|
+
stream.allReady.catch(() => {});
|
|
69
94
|
const headers = new Headers(actionResponse?.headers);
|
|
70
95
|
headers.delete("Content-Length");
|
|
71
96
|
headers.delete("Content-Encoding");
|
|
72
97
|
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
73
98
|
headers.set("Cache-Control", "no-store");
|
|
74
|
-
return new Response(
|
|
99
|
+
return new Response(streamDocument(stream, {
|
|
100
|
+
hydration,
|
|
101
|
+
signal: request.signal,
|
|
102
|
+
nonce: options.nonce,
|
|
103
|
+
dispose: () => router.dispose()
|
|
104
|
+
}), {
|
|
75
105
|
status: snapshot.error ? Number("status" in snapshot.error ? snapshot.error.status : 500) : actionResponse?.status ?? 200,
|
|
76
106
|
headers
|
|
77
107
|
});
|
|
78
108
|
}
|
|
79
|
-
async function
|
|
109
|
+
async function dispatch(request, routes, document = false) {
|
|
80
110
|
const match = matchRoutes(routes.map((route) => ({
|
|
81
111
|
...route,
|
|
82
112
|
path: route.routePath ?? route.path
|
|
@@ -115,5 +145,9 @@ async function dispatchRoute(request, routes, document = false) {
|
|
|
115
145
|
}
|
|
116
146
|
return Response.json(await normalizeResult(value) ?? null);
|
|
117
147
|
}
|
|
148
|
+
async function dispatchRoute(request, routes, document = false) {
|
|
149
|
+
const response = await dispatch(request, routes, document);
|
|
150
|
+
return document ? response : privateResponse(response);
|
|
151
|
+
}
|
|
118
152
|
//#endregion
|
|
119
153
|
export { dispatchRoute, renderApplication };
|
package/dist/server/handler.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface RequestHandlerOptions {
|
|
|
8
8
|
build: ServerBuild;
|
|
9
9
|
/** Browser entry URL resolved from Vite's production manifest. */
|
|
10
10
|
entry: string;
|
|
11
|
+
/** Must match the browser and Vite transport endpoint. */
|
|
12
|
+
dataEndpoint?: string;
|
|
11
13
|
}
|
|
12
14
|
export interface ServerIncomingMessage extends IncomingMessage {
|
|
13
15
|
/** Express supplies this using the application's trust proxy policy. */
|
|
@@ -16,4 +18,4 @@ export interface ServerIncomingMessage extends IncomingMessage {
|
|
|
16
18
|
}
|
|
17
19
|
export type NextHandler = (error?: unknown) => void;
|
|
18
20
|
/** Node/Express middleware; mount after metrics, tracing and static assets. */
|
|
19
|
-
export declare function createRequestHandler({ build, entry, }: RequestHandlerOptions): (incoming: ServerIncomingMessage, outgoing: ServerResponse, next: NextHandler) => Promise<void>;
|
|
21
|
+
export declare function createRequestHandler({ build, entry, dataEndpoint, }: RequestHandlerOptions): (incoming: ServerIncomingMessage, outgoing: ServerResponse, next: NextHandler) => Promise<void>;
|
package/dist/server/handler.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { privateResponse } from "./private-response.js";
|
|
1
2
|
import { send, webRequest } from "./http.js";
|
|
2
3
|
//#region .generated/server/handler.ts
|
|
3
4
|
/** Node/Express middleware; mount after metrics, tracing and static assets. */
|
|
4
|
-
function createRequestHandler({ build, entry }) {
|
|
5
|
+
function createRequestHandler({ build, entry, dataEndpoint = "/_smolter" }) {
|
|
5
6
|
return (incoming, outgoing, next) => {
|
|
6
7
|
const handle = async () => {
|
|
7
8
|
const url = new URL(incoming.originalUrl ?? incoming.url ?? "/", "http://local");
|
|
8
|
-
const dataRequest = url.pathname ===
|
|
9
|
+
const dataRequest = url.pathname === dataEndpoint;
|
|
10
|
+
if (dataRequest) outgoing.setHeader("Cache-Control", "private, no-store");
|
|
9
11
|
const target = dataRequest ? url.searchParams.get("url") : void 0;
|
|
10
12
|
if (dataRequest && (!target?.startsWith("/") || new URL(target, "http://local").origin !== "http://local")) {
|
|
11
13
|
await send(new Response("Invalid route URL", { status: 400 }), outgoing);
|
|
@@ -13,7 +15,7 @@ function createRequestHandler({ build, entry }) {
|
|
|
13
15
|
}
|
|
14
16
|
const request = await webRequest(incoming, outgoing, target ?? void 0);
|
|
15
17
|
const response = dataRequest ? await build.handleDataRequest(request) : await build.default(request, entry);
|
|
16
|
-
await send(response, outgoing);
|
|
18
|
+
await send(dataRequest ? privateResponse(response) : response, outgoing);
|
|
17
19
|
};
|
|
18
20
|
return handle().catch(next);
|
|
19
21
|
};
|
package/dist/server/http.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ServerResponse } from "node:http";
|
|
2
2
|
import type { ServerIncomingMessage } from "./handler.ts";
|
|
3
3
|
export declare function webRequest(incoming: ServerIncomingMessage, outgoing: ServerResponse, target?: string): Promise<Request>;
|
|
4
|
-
|
|
4
|
+
interface StreamingResponse extends ServerResponse {
|
|
5
|
+
flush?: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function send(response: Response, outgoing: StreamingResponse): Promise<void>;
|
|
8
|
+
export {};
|
package/dist/server/http.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Readable, Transform } from "node:stream";
|
|
2
|
+
import { pipeline } from "node:stream/promises";
|
|
1
3
|
//#region .generated/server/http.ts
|
|
2
4
|
async function webRequest(incoming, outgoing, target) {
|
|
3
5
|
const origin = `${incoming.protocol ?? "http"}://${incoming.headers.host}`;
|
|
@@ -21,7 +23,14 @@ async function send(response, outgoing) {
|
|
|
21
23
|
});
|
|
22
24
|
const cookies = response.headers.getSetCookie();
|
|
23
25
|
if (cookies.length) outgoing.setHeader("set-cookie", cookies.map((cookie) => process.env.NODE_ENV === "production" ? cookie : cookie.replace(/; Secure/gi, "")));
|
|
24
|
-
|
|
26
|
+
if (!response.body) {
|
|
27
|
+
outgoing.end();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
await pipeline(Readable.fromWeb(response.body), new Transform({ transform(chunk, _encoding, done) {
|
|
31
|
+
done(null, chunk);
|
|
32
|
+
outgoing.flush?.();
|
|
33
|
+
} }), outgoing);
|
|
25
34
|
}
|
|
26
35
|
//#endregion
|
|
27
36
|
export { send, webRequest };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ import { type Route, type Router } from "../core/index.ts";
|
|
|
6
6
|
/** Create one router per server request, await this, then render the provider. */
|
|
7
7
|
export declare function loadRouter(routes: readonly Route[], input: string | Request): Promise<Router>;
|
|
8
8
|
export { serialize } from "../core/index.ts";
|
|
9
|
-
/** Dispatch
|
|
9
|
+
/** Dispatch a server data request with an explicit private cache policy. */
|
|
10
10
|
export declare function handleRequest(routes: readonly Route[], request: Request): Promise<Response>;
|
package/dist/server/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { safeWrapAsync } from "../core/errors.js";
|
|
|
2
2
|
import { matchRoutes } from "../core/match.js";
|
|
3
3
|
import { createDataRouter } from "../core/router.js";
|
|
4
4
|
import { serialize } from "../core/serialize.js";
|
|
5
|
+
import { privateResponse } from "./private-response.js";
|
|
5
6
|
//#region .generated/server/index.ts
|
|
6
7
|
/**
|
|
7
8
|
* Request-scoped server rendering and HTTP data dispatch.
|
|
@@ -46,7 +47,7 @@ async function loadRouter(routes, input) {
|
|
|
46
47
|
return router;
|
|
47
48
|
}
|
|
48
49
|
/** Dispatch an HTTP data request to a registered server loader or action. */
|
|
49
|
-
async function
|
|
50
|
+
async function dispatchRequest(routes, request) {
|
|
50
51
|
const match = matchRoutes(routes, new URL(request.url).pathname);
|
|
51
52
|
const reading = request.method === "GET";
|
|
52
53
|
if (!match) return Response.json({ error: "No route" }, { status: 404 });
|
|
@@ -67,10 +68,19 @@ async function handleRequest(routes, request) {
|
|
|
67
68
|
if (error) return Response.json({ error: error.message }, { status: 500 });
|
|
68
69
|
if (result.data instanceof Response) {
|
|
69
70
|
const location = result.data.headers.get("Location");
|
|
70
|
-
if (location)
|
|
71
|
+
if (location) {
|
|
72
|
+
const headers = new Headers(result.data.headers);
|
|
73
|
+
headers.delete("Location");
|
|
74
|
+
headers.set("X-Smolter-Redirect", location);
|
|
75
|
+
return new Response(null, { headers });
|
|
76
|
+
}
|
|
71
77
|
return result.data;
|
|
72
78
|
}
|
|
73
79
|
return Response.json(result.data ?? null, { headers: { "Cache-Control": "no-store" } });
|
|
74
80
|
}
|
|
81
|
+
/** Dispatch a server data request with an explicit private cache policy. */
|
|
82
|
+
async function handleRequest(routes, request) {
|
|
83
|
+
return privateResponse(await dispatchRequest(routes, request));
|
|
84
|
+
}
|
|
75
85
|
//#endregion
|
|
76
86
|
export { handleRequest, loadRouter, serialize };
|
package/dist/server/preact.d.ts
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region .generated/server/private-response.ts
|
|
2
|
+
/** Transport data must not be cached by shared intermediaries or the browser. */
|
|
3
|
+
function privateResponse(response) {
|
|
4
|
+
const headers = new Headers(response.headers);
|
|
5
|
+
headers.set("Cache-Control", "private, no-store");
|
|
6
|
+
return new Response(response.body, {
|
|
7
|
+
status: response.status,
|
|
8
|
+
statusText: response.statusText,
|
|
9
|
+
headers
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { privateResponse };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function resolveHydration(value: unknown): Promise<unknown>;
|
|
2
|
+
export interface StreamDocumentOptions {
|
|
3
|
+
hydration: Promise<unknown>;
|
|
4
|
+
nonce?: string;
|
|
5
|
+
signal: AbortSignal;
|
|
6
|
+
dispose: () => void;
|
|
7
|
+
}
|
|
8
|
+
/** Stream the shell immediately, then finalize the JSON snapshot before module scripts execute. */
|
|
9
|
+
export declare function streamDocument(source: ReadableStream<Uint8Array>, options: StreamDocumentOptions): ReadableStream<Uint8Array>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { serializeData } from "../core/serialize.js";
|
|
2
|
+
//#region .generated/server/stream-document.ts
|
|
3
|
+
async function resolveHydration(value) {
|
|
4
|
+
if (value instanceof Promise) return value.then(resolveHydration, (error) => ({ __smolterRejected: { message: error instanceof Error ? error.message : String(error) } }));
|
|
5
|
+
if (Array.isArray(value)) return Promise.all(value.map(resolveHydration));
|
|
6
|
+
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
7
|
+
const entries = await Promise.all(Object.entries(value).map(async ([key, field]) => [key, await resolveHydration(field)]));
|
|
8
|
+
return Object.fromEntries(entries);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
/** Stream the shell immediately, then finalize the JSON snapshot before module scripts execute. */
|
|
13
|
+
function streamDocument(source, options) {
|
|
14
|
+
const reader = source.getReader();
|
|
15
|
+
const encoder = new TextEncoder();
|
|
16
|
+
const decoder = new TextDecoder();
|
|
17
|
+
let suffix = "";
|
|
18
|
+
let first = true;
|
|
19
|
+
let cancelled = false;
|
|
20
|
+
let finish;
|
|
21
|
+
const interrupted = new Promise((resolve) => {
|
|
22
|
+
finish = resolve;
|
|
23
|
+
});
|
|
24
|
+
const abort = () => {
|
|
25
|
+
finish();
|
|
26
|
+
reader.cancel().catch(() => {});
|
|
27
|
+
};
|
|
28
|
+
options.signal.addEventListener("abort", abort, { once: true });
|
|
29
|
+
if (options.signal.aborted) abort();
|
|
30
|
+
function dispose() {
|
|
31
|
+
options.signal.removeEventListener("abort", abort);
|
|
32
|
+
options.dispose();
|
|
33
|
+
}
|
|
34
|
+
return new ReadableStream({
|
|
35
|
+
async pull(controller) {
|
|
36
|
+
const result = await reader.read().catch((error) => ({ error }));
|
|
37
|
+
if ("error" in result) {
|
|
38
|
+
dispose();
|
|
39
|
+
controller.error(result.error);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (result.done) {
|
|
43
|
+
if (!options.signal.aborted) {
|
|
44
|
+
const value = await Promise.race([options.hydration, interrupted]);
|
|
45
|
+
if (cancelled) return;
|
|
46
|
+
if (options.signal.aborted) {
|
|
47
|
+
dispose();
|
|
48
|
+
controller.close();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const hydration = serializeData(value);
|
|
52
|
+
const nonce = options.nonce ? ` nonce="${options.nonce.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<")}"` : "";
|
|
53
|
+
controller.enqueue(encoder.encode(`<script type="application/json"${nonce}>${hydration}<\/script><script${nonce}>(function(){var script=document.currentScript,payload=script.previousElementSibling,data=document.getElementById("smolter-data");if(data)data.textContent=payload.textContent;payload.remove();script.remove()})()<\/script>${suffix}`));
|
|
54
|
+
}
|
|
55
|
+
dispose();
|
|
56
|
+
controller.close();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let chunk = decoder.decode(result.value, { stream: true });
|
|
60
|
+
if (first && !/^<!doctype/i.test(chunk)) chunk = `<!doctype html>${chunk}`;
|
|
61
|
+
first = false;
|
|
62
|
+
const closing = chunk.lastIndexOf("</body>");
|
|
63
|
+
if (closing !== -1) {
|
|
64
|
+
suffix = chunk.slice(closing);
|
|
65
|
+
chunk = chunk.slice(0, closing);
|
|
66
|
+
}
|
|
67
|
+
controller.enqueue(encoder.encode(chunk));
|
|
68
|
+
},
|
|
69
|
+
async cancel() {
|
|
70
|
+
cancelled = true;
|
|
71
|
+
finish();
|
|
72
|
+
dispose();
|
|
73
|
+
await reader.cancel();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { resolveHydration, streamDocument };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type Plugin } from "vite";
|
|
2
|
-
export declare function lazyFiles(directory: string): Plugin;
|
|
2
|
+
export declare function lazyFiles(directory: string, dataEndpoint?: string): Plugin;
|