litzjs 0.0.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/LICENSE +21 -0
- package/README.md +936 -0
- package/dist/bindings-B1P6pL93.js +21 -0
- package/dist/bindings-BDe-v5i6.mjs +10 -0
- package/dist/chunk-8l464Juk.js +28 -0
- package/dist/client.d.mts +35 -0
- package/dist/client.d.ts +35 -0
- package/dist/client.js +1633 -0
- package/dist/client.mjs +1625 -0
- package/dist/index.d.mts +559 -0
- package/dist/index.d.ts +559 -0
- package/dist/index.js +360 -0
- package/dist/index.mjs +344 -0
- package/dist/internal-transport-DR0r68ff.js +161 -0
- package/dist/internal-transport-dsMykcNK.mjs +114 -0
- package/dist/request-headers-DepZ5tjg.mjs +35 -0
- package/dist/request-headers-ZPR3TQs3.js +46 -0
- package/dist/server.d.mts +74 -0
- package/dist/server.d.ts +74 -0
- package/dist/server.js +316 -0
- package/dist/server.mjs +315 -0
- package/dist/vite.d.mts +10043 -0
- package/dist/vite.d.ts +10043 -0
- package/dist/vite.js +1481 -0
- package/dist/vite.mjs +1474 -0
- package/package.json +90 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_internal_transport = require("./internal-transport-DR0r68ff.js");
|
|
3
|
+
const require_request_headers = require("./request-headers-ZPR3TQs3.js");
|
|
4
|
+
//#region src/server/index.ts
|
|
5
|
+
let rscRendererPromise;
|
|
6
|
+
function createServer(options = {}) {
|
|
7
|
+
const manifest = options.manifest ?? {};
|
|
8
|
+
async function handle(request) {
|
|
9
|
+
const url = new URL(request.url);
|
|
10
|
+
let contextLoaded = false;
|
|
11
|
+
let contextValue;
|
|
12
|
+
async function getContext() {
|
|
13
|
+
if (contextLoaded) return contextValue;
|
|
14
|
+
contextLoaded = true;
|
|
15
|
+
contextValue = options.createContext ? await options.createContext(request) : void 0;
|
|
16
|
+
return contextValue;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
if (url.pathname === "/_litzjs/resource") return handleResourceRequest(request, manifest.resources ?? [], getContext);
|
|
20
|
+
if (url.pathname === "/_litzjs/route" || url.pathname === "/_litzjs/action") return handleRouteRequest(request, manifest.routes ?? [], getContext);
|
|
21
|
+
const apiResponse = await handleApiRequest(request, manifest.apiRoutes ?? [], getContext);
|
|
22
|
+
if (apiResponse) return apiResponse;
|
|
23
|
+
if (request.method === "GET" || request.method === "HEAD") {
|
|
24
|
+
const assetResponse = await options.assets?.(request);
|
|
25
|
+
if (assetResponse) return toHeadResponseIfNeeded(request, assetResponse);
|
|
26
|
+
if (shouldServeDocument(request, url.pathname)) {
|
|
27
|
+
const documentResponse = await createDocumentResponse(options.document, request);
|
|
28
|
+
if (documentResponse) return toHeadResponseIfNeeded(request, documentResponse);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return new Response("Not Found", { status: 404 });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const context = contextLoaded ? contextValue : void 0;
|
|
34
|
+
options.onError?.(error, context);
|
|
35
|
+
return new Response("Litz server error.", {
|
|
36
|
+
status: 500,
|
|
37
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { fetch: handle };
|
|
42
|
+
}
|
|
43
|
+
async function createDocumentResponse(document, request) {
|
|
44
|
+
if (!document) return null;
|
|
45
|
+
if (typeof document === "string") return new Response(request.method === "HEAD" ? null : document, {
|
|
46
|
+
status: 200,
|
|
47
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
48
|
+
});
|
|
49
|
+
return document(request);
|
|
50
|
+
}
|
|
51
|
+
function toHeadResponseIfNeeded(request, response) {
|
|
52
|
+
if (request.method !== "HEAD") return response;
|
|
53
|
+
return new Response(null, {
|
|
54
|
+
status: response.status,
|
|
55
|
+
statusText: response.statusText,
|
|
56
|
+
headers: response.headers
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async function handleResourceRequest(request, resources, getContext) {
|
|
60
|
+
if (request.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
|
|
61
|
+
const body = await require_request_headers.parseInternalRequestBody(request);
|
|
62
|
+
const resourcePath = body.path;
|
|
63
|
+
const operation = body.operation ?? "loader";
|
|
64
|
+
const entry = resources.find((resource) => resource.path === resourcePath);
|
|
65
|
+
if (!resourcePath || !entry?.resource) return createLitzJsonResponse(404, {
|
|
66
|
+
kind: "error",
|
|
67
|
+
message: "Resource not found."
|
|
68
|
+
});
|
|
69
|
+
const handler = operation === "action" ? entry.resource.action : entry.resource.loader;
|
|
70
|
+
const middleware = entry.resource.middleware ?? [];
|
|
71
|
+
if (!handler) return createLitzJsonResponse(405, {
|
|
72
|
+
kind: "error",
|
|
73
|
+
message: `Resource does not define a ${operation}.`
|
|
74
|
+
});
|
|
75
|
+
const normalizedRequest = normalizeInternalRequest(request, resourcePath, body.request, body.payload);
|
|
76
|
+
const signal = request.signal;
|
|
77
|
+
const context = await getContext();
|
|
78
|
+
return createServerResultResponse(await runMiddlewareChain({
|
|
79
|
+
middleware,
|
|
80
|
+
request: normalizedRequest.request,
|
|
81
|
+
params: normalizedRequest.params,
|
|
82
|
+
signal,
|
|
83
|
+
context,
|
|
84
|
+
execute(nextContext) {
|
|
85
|
+
return handler({
|
|
86
|
+
request: normalizedRequest.request,
|
|
87
|
+
params: normalizedRequest.params,
|
|
88
|
+
signal,
|
|
89
|
+
context: nextContext
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}), `${entry.path}#${operation}`);
|
|
93
|
+
}
|
|
94
|
+
async function handleRouteRequest(request, routes, getContext) {
|
|
95
|
+
if (request.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
|
|
96
|
+
const body = await require_request_headers.parseInternalRequestBody(request);
|
|
97
|
+
const routePath = body.path;
|
|
98
|
+
const targetId = body.target;
|
|
99
|
+
const operation = body.operation ?? (new URL(request.url).pathname === "/_litzjs/action" ? "action" : "loader");
|
|
100
|
+
const entry = routes.find((route) => route.path === routePath);
|
|
101
|
+
if (!routePath || !entry?.route) return createLitzJsonResponse(404, {
|
|
102
|
+
kind: "error",
|
|
103
|
+
message: "Route not found."
|
|
104
|
+
});
|
|
105
|
+
const chain = getRouteMatchChain(entry);
|
|
106
|
+
const target = operation === "action" ? chain[chain.length - 1] : findTargetRouteMatch(chain, targetId ?? routePath);
|
|
107
|
+
if (!target) return createLitzJsonResponse(404, {
|
|
108
|
+
kind: "error",
|
|
109
|
+
message: "Route target not found."
|
|
110
|
+
});
|
|
111
|
+
const targetIndex = chain.findIndex((candidate) => candidate.id === target.id);
|
|
112
|
+
const handler = operation === "action" ? entry.route.action ?? entry.route.options?.action : target.loader;
|
|
113
|
+
const middleware = chain.slice(0, targetIndex + 1).flatMap((candidate) => candidate.middleware);
|
|
114
|
+
if (!handler) return createLitzJsonResponse(405, {
|
|
115
|
+
kind: "error",
|
|
116
|
+
message: `Route does not define a ${operation}.`
|
|
117
|
+
});
|
|
118
|
+
const normalizedRequest = normalizeInternalRequest(request, routePath, body.request, body.payload);
|
|
119
|
+
const signal = request.signal;
|
|
120
|
+
const context = await getContext();
|
|
121
|
+
return createServerResultResponse(await runMiddlewareChain({
|
|
122
|
+
middleware,
|
|
123
|
+
request: normalizedRequest.request,
|
|
124
|
+
params: operation === "action" ? normalizedRequest.params : require_internal_transport.extractRouteLikeParams(target.path, new URL(normalizedRequest.request.url).pathname) ?? normalizedRequest.params,
|
|
125
|
+
signal,
|
|
126
|
+
context,
|
|
127
|
+
execute(nextContext) {
|
|
128
|
+
const params = operation === "action" ? normalizedRequest.params : require_internal_transport.extractRouteLikeParams(target.path, new URL(normalizedRequest.request.url).pathname) ?? normalizedRequest.params;
|
|
129
|
+
return handler({
|
|
130
|
+
request: normalizedRequest.request,
|
|
131
|
+
params,
|
|
132
|
+
signal,
|
|
133
|
+
context: nextContext
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}), `${target.id}#${operation}`);
|
|
137
|
+
}
|
|
138
|
+
async function handleApiRequest(request, apiRoutes, getContext) {
|
|
139
|
+
const url = new URL(request.url);
|
|
140
|
+
const matched = apiRoutes.map((candidate) => ({
|
|
141
|
+
entry: candidate,
|
|
142
|
+
params: require_internal_transport.matchPathname(candidate.path, url.pathname)
|
|
143
|
+
})).find((candidate) => candidate.params !== null);
|
|
144
|
+
if (!matched?.entry.api?.methods) return null;
|
|
145
|
+
const params = matched.params;
|
|
146
|
+
if (!params) return null;
|
|
147
|
+
const method = request.method.toUpperCase();
|
|
148
|
+
const handler = matched.entry.api.methods[method] ?? matched.entry.api.methods.ALL;
|
|
149
|
+
const middleware = matched.entry.api.middleware ?? [];
|
|
150
|
+
if (!handler) return new Response("Method Not Allowed", { status: 405 });
|
|
151
|
+
const signal = request.signal;
|
|
152
|
+
return runMiddlewareChain({
|
|
153
|
+
middleware,
|
|
154
|
+
request,
|
|
155
|
+
params,
|
|
156
|
+
signal,
|
|
157
|
+
context: await getContext(),
|
|
158
|
+
execute(nextContext) {
|
|
159
|
+
return handler({
|
|
160
|
+
request,
|
|
161
|
+
params,
|
|
162
|
+
signal,
|
|
163
|
+
context: nextContext
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
async function runMiddlewareChain(options) {
|
|
169
|
+
const dispatch = async (index, context) => {
|
|
170
|
+
const middleware = options.middleware[index];
|
|
171
|
+
if (!middleware) return options.execute(context);
|
|
172
|
+
let called = false;
|
|
173
|
+
return middleware({
|
|
174
|
+
request: options.request,
|
|
175
|
+
params: options.params,
|
|
176
|
+
signal: options.signal,
|
|
177
|
+
context
|
|
178
|
+
}, async (overrides) => {
|
|
179
|
+
if (called) throw new Error("Litz middleware next() called multiple times.");
|
|
180
|
+
called = true;
|
|
181
|
+
const nextContext = overrides && Object.prototype.hasOwnProperty.call(overrides, "context") ? overrides.context : context;
|
|
182
|
+
return dispatch(index + 1, nextContext);
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
return dispatch(0, options.context);
|
|
186
|
+
}
|
|
187
|
+
function getRouteMatchChain(entry) {
|
|
188
|
+
return [...collectLayouts(entry.route?.options?.layout).map((layout) => ({
|
|
189
|
+
id: layout.id,
|
|
190
|
+
path: layout.path,
|
|
191
|
+
loader: layout.options?.loader,
|
|
192
|
+
middleware: layout.options?.middleware ?? []
|
|
193
|
+
})), {
|
|
194
|
+
id: entry.id,
|
|
195
|
+
path: entry.path,
|
|
196
|
+
loader: entry.route?.loader ?? entry.route?.options?.loader,
|
|
197
|
+
middleware: entry.route?.options?.middleware ?? []
|
|
198
|
+
}];
|
|
199
|
+
}
|
|
200
|
+
function collectLayouts(layout) {
|
|
201
|
+
if (!layout) return [];
|
|
202
|
+
return [...collectLayouts(layout.options?.layout), layout];
|
|
203
|
+
}
|
|
204
|
+
function findTargetRouteMatch(chain, targetId) {
|
|
205
|
+
return chain.find((entry) => entry.id === targetId);
|
|
206
|
+
}
|
|
207
|
+
async function createServerResultResponse(result, viewId = "litzjs#view") {
|
|
208
|
+
if (!result || typeof result !== "object" || !("kind" in result)) return createLitzJsonResponse(500, {
|
|
209
|
+
kind: "fault",
|
|
210
|
+
message: "Handler returned an unknown result."
|
|
211
|
+
});
|
|
212
|
+
const serverResult = result;
|
|
213
|
+
const headers = new Headers(serverResult.headers);
|
|
214
|
+
applyRevalidateHeader(headers, serverResult.revalidate);
|
|
215
|
+
switch (serverResult.kind) {
|
|
216
|
+
case "data": return createLitzJsonResponse(serverResult.status ?? 200, {
|
|
217
|
+
kind: "data",
|
|
218
|
+
data: serverResult.data,
|
|
219
|
+
revalidate: serverResult.revalidate ?? []
|
|
220
|
+
}, headers);
|
|
221
|
+
case "invalid": return createLitzJsonResponse(serverResult.status ?? 422, {
|
|
222
|
+
kind: "invalid",
|
|
223
|
+
fields: serverResult.fields,
|
|
224
|
+
formError: serverResult.formError,
|
|
225
|
+
data: serverResult.data
|
|
226
|
+
}, headers);
|
|
227
|
+
case "redirect": return createLitzJsonResponse(serverResult.status ?? 303, {
|
|
228
|
+
kind: "redirect",
|
|
229
|
+
location: serverResult.location,
|
|
230
|
+
replace: serverResult.replace ?? false,
|
|
231
|
+
revalidate: serverResult.revalidate ?? []
|
|
232
|
+
}, headers);
|
|
233
|
+
case "error": return createLitzJsonResponse(serverResult.status ?? 500, {
|
|
234
|
+
kind: "error",
|
|
235
|
+
message: serverResult.message ?? "Error",
|
|
236
|
+
code: serverResult.code,
|
|
237
|
+
data: serverResult.data
|
|
238
|
+
}, headers);
|
|
239
|
+
case "fault": return createLitzJsonResponse(serverResult.status ?? 500, {
|
|
240
|
+
kind: "fault",
|
|
241
|
+
message: serverResult.message ?? "Fault",
|
|
242
|
+
digest: serverResult.digest
|
|
243
|
+
}, headers);
|
|
244
|
+
case "view": {
|
|
245
|
+
headers.set("content-type", "text/x-component");
|
|
246
|
+
headers.set("x-litzjs-kind", "view");
|
|
247
|
+
headers.set("x-litzjs-status", String(serverResult.status ?? 200));
|
|
248
|
+
headers.set("x-litzjs-view-id", viewId);
|
|
249
|
+
const stream = (await loadRscRenderer()).renderToReadableStream(serverResult.node);
|
|
250
|
+
return new Response(stream, {
|
|
251
|
+
status: serverResult.status ?? 200,
|
|
252
|
+
headers
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
default: return createLitzJsonResponse(500, {
|
|
256
|
+
kind: "fault",
|
|
257
|
+
message: `Unsupported result kind "${serverResult.kind}".`
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async function loadRscRenderer() {
|
|
262
|
+
rscRendererPromise ??= import("@vitejs/plugin-rsc/rsc");
|
|
263
|
+
return rscRendererPromise;
|
|
264
|
+
}
|
|
265
|
+
function createLitzJsonResponse(status, body, headers) {
|
|
266
|
+
const responseHeaders = new Headers(headers);
|
|
267
|
+
responseHeaders.set("content-type", "application/vnd.litzjs.result+json");
|
|
268
|
+
return new Response(JSON.stringify(body), {
|
|
269
|
+
status,
|
|
270
|
+
headers: responseHeaders
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
function applyRevalidateHeader(headers, revalidate) {
|
|
274
|
+
if (!revalidate?.length) return;
|
|
275
|
+
headers.set("x-litzjs-revalidate", revalidate.join(","));
|
|
276
|
+
}
|
|
277
|
+
function normalizeInternalRequest(originalRequest, pathPattern, requestData, payload) {
|
|
278
|
+
const params = requestData?.params ?? {};
|
|
279
|
+
const search = new URLSearchParams(requestData?.search ?? {});
|
|
280
|
+
const url = new URL(originalRequest.url);
|
|
281
|
+
url.pathname = interpolatePath(pathPattern, params);
|
|
282
|
+
url.search = search.toString();
|
|
283
|
+
url.hash = "";
|
|
284
|
+
let body;
|
|
285
|
+
if (payload) {
|
|
286
|
+
body = new FormData();
|
|
287
|
+
for (const [key, value] of payload.entries) body.append(key, value);
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
request: new Request(url, {
|
|
291
|
+
method: body ? "POST" : "GET",
|
|
292
|
+
headers: require_request_headers.createInternalHandlerHeaders(originalRequest.headers),
|
|
293
|
+
signal: originalRequest.signal,
|
|
294
|
+
body
|
|
295
|
+
}),
|
|
296
|
+
params
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function interpolatePath(pathPattern, params) {
|
|
300
|
+
return pathPattern.replace(/:([A-Za-z0-9_]+)/g, (_, key) => {
|
|
301
|
+
const value = params[key];
|
|
302
|
+
if (value === void 0) throw new Error(`Missing required path param "${key}" for path "${pathPattern}".`);
|
|
303
|
+
return encodeURIComponent(value);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function shouldServeDocument(request, pathname) {
|
|
307
|
+
if (pathname.startsWith("/_litzjs/") || pathname.startsWith("/api/")) return false;
|
|
308
|
+
if (lastPathSegment(pathname).includes(".")) return false;
|
|
309
|
+
const accept = request.headers.get("accept") ?? "";
|
|
310
|
+
return accept.includes("text/html") || accept.includes("*/*");
|
|
311
|
+
}
|
|
312
|
+
function lastPathSegment(pathname) {
|
|
313
|
+
return require_internal_transport.trimPathSegments(pathname).at(-1) ?? "";
|
|
314
|
+
}
|
|
315
|
+
//#endregion
|
|
316
|
+
exports.createServer = createServer;
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { a as matchPathname, i as extractRouteLikeParams, s as trimPathSegments } from "./internal-transport-dsMykcNK.mjs";
|
|
2
|
+
import { n as parseInternalRequestBody, t as createInternalHandlerHeaders } from "./request-headers-DepZ5tjg.mjs";
|
|
3
|
+
//#region src/server/index.ts
|
|
4
|
+
let rscRendererPromise;
|
|
5
|
+
function createServer(options = {}) {
|
|
6
|
+
const manifest = options.manifest ?? {};
|
|
7
|
+
async function handle(request) {
|
|
8
|
+
const url = new URL(request.url);
|
|
9
|
+
let contextLoaded = false;
|
|
10
|
+
let contextValue;
|
|
11
|
+
async function getContext() {
|
|
12
|
+
if (contextLoaded) return contextValue;
|
|
13
|
+
contextLoaded = true;
|
|
14
|
+
contextValue = options.createContext ? await options.createContext(request) : void 0;
|
|
15
|
+
return contextValue;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
if (url.pathname === "/_litzjs/resource") return handleResourceRequest(request, manifest.resources ?? [], getContext);
|
|
19
|
+
if (url.pathname === "/_litzjs/route" || url.pathname === "/_litzjs/action") return handleRouteRequest(request, manifest.routes ?? [], getContext);
|
|
20
|
+
const apiResponse = await handleApiRequest(request, manifest.apiRoutes ?? [], getContext);
|
|
21
|
+
if (apiResponse) return apiResponse;
|
|
22
|
+
if (request.method === "GET" || request.method === "HEAD") {
|
|
23
|
+
const assetResponse = await options.assets?.(request);
|
|
24
|
+
if (assetResponse) return toHeadResponseIfNeeded(request, assetResponse);
|
|
25
|
+
if (shouldServeDocument(request, url.pathname)) {
|
|
26
|
+
const documentResponse = await createDocumentResponse(options.document, request);
|
|
27
|
+
if (documentResponse) return toHeadResponseIfNeeded(request, documentResponse);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return new Response("Not Found", { status: 404 });
|
|
31
|
+
} catch (error) {
|
|
32
|
+
const context = contextLoaded ? contextValue : void 0;
|
|
33
|
+
options.onError?.(error, context);
|
|
34
|
+
return new Response("Litz server error.", {
|
|
35
|
+
status: 500,
|
|
36
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { fetch: handle };
|
|
41
|
+
}
|
|
42
|
+
async function createDocumentResponse(document, request) {
|
|
43
|
+
if (!document) return null;
|
|
44
|
+
if (typeof document === "string") return new Response(request.method === "HEAD" ? null : document, {
|
|
45
|
+
status: 200,
|
|
46
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
47
|
+
});
|
|
48
|
+
return document(request);
|
|
49
|
+
}
|
|
50
|
+
function toHeadResponseIfNeeded(request, response) {
|
|
51
|
+
if (request.method !== "HEAD") return response;
|
|
52
|
+
return new Response(null, {
|
|
53
|
+
status: response.status,
|
|
54
|
+
statusText: response.statusText,
|
|
55
|
+
headers: response.headers
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function handleResourceRequest(request, resources, getContext) {
|
|
59
|
+
if (request.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
|
|
60
|
+
const body = await parseInternalRequestBody(request);
|
|
61
|
+
const resourcePath = body.path;
|
|
62
|
+
const operation = body.operation ?? "loader";
|
|
63
|
+
const entry = resources.find((resource) => resource.path === resourcePath);
|
|
64
|
+
if (!resourcePath || !entry?.resource) return createLitzJsonResponse(404, {
|
|
65
|
+
kind: "error",
|
|
66
|
+
message: "Resource not found."
|
|
67
|
+
});
|
|
68
|
+
const handler = operation === "action" ? entry.resource.action : entry.resource.loader;
|
|
69
|
+
const middleware = entry.resource.middleware ?? [];
|
|
70
|
+
if (!handler) return createLitzJsonResponse(405, {
|
|
71
|
+
kind: "error",
|
|
72
|
+
message: `Resource does not define a ${operation}.`
|
|
73
|
+
});
|
|
74
|
+
const normalizedRequest = normalizeInternalRequest(request, resourcePath, body.request, body.payload);
|
|
75
|
+
const signal = request.signal;
|
|
76
|
+
const context = await getContext();
|
|
77
|
+
return createServerResultResponse(await runMiddlewareChain({
|
|
78
|
+
middleware,
|
|
79
|
+
request: normalizedRequest.request,
|
|
80
|
+
params: normalizedRequest.params,
|
|
81
|
+
signal,
|
|
82
|
+
context,
|
|
83
|
+
execute(nextContext) {
|
|
84
|
+
return handler({
|
|
85
|
+
request: normalizedRequest.request,
|
|
86
|
+
params: normalizedRequest.params,
|
|
87
|
+
signal,
|
|
88
|
+
context: nextContext
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}), `${entry.path}#${operation}`);
|
|
92
|
+
}
|
|
93
|
+
async function handleRouteRequest(request, routes, getContext) {
|
|
94
|
+
if (request.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
|
|
95
|
+
const body = await parseInternalRequestBody(request);
|
|
96
|
+
const routePath = body.path;
|
|
97
|
+
const targetId = body.target;
|
|
98
|
+
const operation = body.operation ?? (new URL(request.url).pathname === "/_litzjs/action" ? "action" : "loader");
|
|
99
|
+
const entry = routes.find((route) => route.path === routePath);
|
|
100
|
+
if (!routePath || !entry?.route) return createLitzJsonResponse(404, {
|
|
101
|
+
kind: "error",
|
|
102
|
+
message: "Route not found."
|
|
103
|
+
});
|
|
104
|
+
const chain = getRouteMatchChain(entry);
|
|
105
|
+
const target = operation === "action" ? chain[chain.length - 1] : findTargetRouteMatch(chain, targetId ?? routePath);
|
|
106
|
+
if (!target) return createLitzJsonResponse(404, {
|
|
107
|
+
kind: "error",
|
|
108
|
+
message: "Route target not found."
|
|
109
|
+
});
|
|
110
|
+
const targetIndex = chain.findIndex((candidate) => candidate.id === target.id);
|
|
111
|
+
const handler = operation === "action" ? entry.route.action ?? entry.route.options?.action : target.loader;
|
|
112
|
+
const middleware = chain.slice(0, targetIndex + 1).flatMap((candidate) => candidate.middleware);
|
|
113
|
+
if (!handler) return createLitzJsonResponse(405, {
|
|
114
|
+
kind: "error",
|
|
115
|
+
message: `Route does not define a ${operation}.`
|
|
116
|
+
});
|
|
117
|
+
const normalizedRequest = normalizeInternalRequest(request, routePath, body.request, body.payload);
|
|
118
|
+
const signal = request.signal;
|
|
119
|
+
const context = await getContext();
|
|
120
|
+
return createServerResultResponse(await runMiddlewareChain({
|
|
121
|
+
middleware,
|
|
122
|
+
request: normalizedRequest.request,
|
|
123
|
+
params: operation === "action" ? normalizedRequest.params : extractRouteLikeParams(target.path, new URL(normalizedRequest.request.url).pathname) ?? normalizedRequest.params,
|
|
124
|
+
signal,
|
|
125
|
+
context,
|
|
126
|
+
execute(nextContext) {
|
|
127
|
+
const params = operation === "action" ? normalizedRequest.params : extractRouteLikeParams(target.path, new URL(normalizedRequest.request.url).pathname) ?? normalizedRequest.params;
|
|
128
|
+
return handler({
|
|
129
|
+
request: normalizedRequest.request,
|
|
130
|
+
params,
|
|
131
|
+
signal,
|
|
132
|
+
context: nextContext
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}), `${target.id}#${operation}`);
|
|
136
|
+
}
|
|
137
|
+
async function handleApiRequest(request, apiRoutes, getContext) {
|
|
138
|
+
const url = new URL(request.url);
|
|
139
|
+
const matched = apiRoutes.map((candidate) => ({
|
|
140
|
+
entry: candidate,
|
|
141
|
+
params: matchPathname(candidate.path, url.pathname)
|
|
142
|
+
})).find((candidate) => candidate.params !== null);
|
|
143
|
+
if (!matched?.entry.api?.methods) return null;
|
|
144
|
+
const params = matched.params;
|
|
145
|
+
if (!params) return null;
|
|
146
|
+
const method = request.method.toUpperCase();
|
|
147
|
+
const handler = matched.entry.api.methods[method] ?? matched.entry.api.methods.ALL;
|
|
148
|
+
const middleware = matched.entry.api.middleware ?? [];
|
|
149
|
+
if (!handler) return new Response("Method Not Allowed", { status: 405 });
|
|
150
|
+
const signal = request.signal;
|
|
151
|
+
return runMiddlewareChain({
|
|
152
|
+
middleware,
|
|
153
|
+
request,
|
|
154
|
+
params,
|
|
155
|
+
signal,
|
|
156
|
+
context: await getContext(),
|
|
157
|
+
execute(nextContext) {
|
|
158
|
+
return handler({
|
|
159
|
+
request,
|
|
160
|
+
params,
|
|
161
|
+
signal,
|
|
162
|
+
context: nextContext
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function runMiddlewareChain(options) {
|
|
168
|
+
const dispatch = async (index, context) => {
|
|
169
|
+
const middleware = options.middleware[index];
|
|
170
|
+
if (!middleware) return options.execute(context);
|
|
171
|
+
let called = false;
|
|
172
|
+
return middleware({
|
|
173
|
+
request: options.request,
|
|
174
|
+
params: options.params,
|
|
175
|
+
signal: options.signal,
|
|
176
|
+
context
|
|
177
|
+
}, async (overrides) => {
|
|
178
|
+
if (called) throw new Error("Litz middleware next() called multiple times.");
|
|
179
|
+
called = true;
|
|
180
|
+
const nextContext = overrides && Object.prototype.hasOwnProperty.call(overrides, "context") ? overrides.context : context;
|
|
181
|
+
return dispatch(index + 1, nextContext);
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
return dispatch(0, options.context);
|
|
185
|
+
}
|
|
186
|
+
function getRouteMatchChain(entry) {
|
|
187
|
+
return [...collectLayouts(entry.route?.options?.layout).map((layout) => ({
|
|
188
|
+
id: layout.id,
|
|
189
|
+
path: layout.path,
|
|
190
|
+
loader: layout.options?.loader,
|
|
191
|
+
middleware: layout.options?.middleware ?? []
|
|
192
|
+
})), {
|
|
193
|
+
id: entry.id,
|
|
194
|
+
path: entry.path,
|
|
195
|
+
loader: entry.route?.loader ?? entry.route?.options?.loader,
|
|
196
|
+
middleware: entry.route?.options?.middleware ?? []
|
|
197
|
+
}];
|
|
198
|
+
}
|
|
199
|
+
function collectLayouts(layout) {
|
|
200
|
+
if (!layout) return [];
|
|
201
|
+
return [...collectLayouts(layout.options?.layout), layout];
|
|
202
|
+
}
|
|
203
|
+
function findTargetRouteMatch(chain, targetId) {
|
|
204
|
+
return chain.find((entry) => entry.id === targetId);
|
|
205
|
+
}
|
|
206
|
+
async function createServerResultResponse(result, viewId = "litzjs#view") {
|
|
207
|
+
if (!result || typeof result !== "object" || !("kind" in result)) return createLitzJsonResponse(500, {
|
|
208
|
+
kind: "fault",
|
|
209
|
+
message: "Handler returned an unknown result."
|
|
210
|
+
});
|
|
211
|
+
const serverResult = result;
|
|
212
|
+
const headers = new Headers(serverResult.headers);
|
|
213
|
+
applyRevalidateHeader(headers, serverResult.revalidate);
|
|
214
|
+
switch (serverResult.kind) {
|
|
215
|
+
case "data": return createLitzJsonResponse(serverResult.status ?? 200, {
|
|
216
|
+
kind: "data",
|
|
217
|
+
data: serverResult.data,
|
|
218
|
+
revalidate: serverResult.revalidate ?? []
|
|
219
|
+
}, headers);
|
|
220
|
+
case "invalid": return createLitzJsonResponse(serverResult.status ?? 422, {
|
|
221
|
+
kind: "invalid",
|
|
222
|
+
fields: serverResult.fields,
|
|
223
|
+
formError: serverResult.formError,
|
|
224
|
+
data: serverResult.data
|
|
225
|
+
}, headers);
|
|
226
|
+
case "redirect": return createLitzJsonResponse(serverResult.status ?? 303, {
|
|
227
|
+
kind: "redirect",
|
|
228
|
+
location: serverResult.location,
|
|
229
|
+
replace: serverResult.replace ?? false,
|
|
230
|
+
revalidate: serverResult.revalidate ?? []
|
|
231
|
+
}, headers);
|
|
232
|
+
case "error": return createLitzJsonResponse(serverResult.status ?? 500, {
|
|
233
|
+
kind: "error",
|
|
234
|
+
message: serverResult.message ?? "Error",
|
|
235
|
+
code: serverResult.code,
|
|
236
|
+
data: serverResult.data
|
|
237
|
+
}, headers);
|
|
238
|
+
case "fault": return createLitzJsonResponse(serverResult.status ?? 500, {
|
|
239
|
+
kind: "fault",
|
|
240
|
+
message: serverResult.message ?? "Fault",
|
|
241
|
+
digest: serverResult.digest
|
|
242
|
+
}, headers);
|
|
243
|
+
case "view": {
|
|
244
|
+
headers.set("content-type", "text/x-component");
|
|
245
|
+
headers.set("x-litzjs-kind", "view");
|
|
246
|
+
headers.set("x-litzjs-status", String(serverResult.status ?? 200));
|
|
247
|
+
headers.set("x-litzjs-view-id", viewId);
|
|
248
|
+
const stream = (await loadRscRenderer()).renderToReadableStream(serverResult.node);
|
|
249
|
+
return new Response(stream, {
|
|
250
|
+
status: serverResult.status ?? 200,
|
|
251
|
+
headers
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
default: return createLitzJsonResponse(500, {
|
|
255
|
+
kind: "fault",
|
|
256
|
+
message: `Unsupported result kind "${serverResult.kind}".`
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function loadRscRenderer() {
|
|
261
|
+
rscRendererPromise ??= import("@vitejs/plugin-rsc/rsc");
|
|
262
|
+
return rscRendererPromise;
|
|
263
|
+
}
|
|
264
|
+
function createLitzJsonResponse(status, body, headers) {
|
|
265
|
+
const responseHeaders = new Headers(headers);
|
|
266
|
+
responseHeaders.set("content-type", "application/vnd.litzjs.result+json");
|
|
267
|
+
return new Response(JSON.stringify(body), {
|
|
268
|
+
status,
|
|
269
|
+
headers: responseHeaders
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function applyRevalidateHeader(headers, revalidate) {
|
|
273
|
+
if (!revalidate?.length) return;
|
|
274
|
+
headers.set("x-litzjs-revalidate", revalidate.join(","));
|
|
275
|
+
}
|
|
276
|
+
function normalizeInternalRequest(originalRequest, pathPattern, requestData, payload) {
|
|
277
|
+
const params = requestData?.params ?? {};
|
|
278
|
+
const search = new URLSearchParams(requestData?.search ?? {});
|
|
279
|
+
const url = new URL(originalRequest.url);
|
|
280
|
+
url.pathname = interpolatePath(pathPattern, params);
|
|
281
|
+
url.search = search.toString();
|
|
282
|
+
url.hash = "";
|
|
283
|
+
let body;
|
|
284
|
+
if (payload) {
|
|
285
|
+
body = new FormData();
|
|
286
|
+
for (const [key, value] of payload.entries) body.append(key, value);
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
request: new Request(url, {
|
|
290
|
+
method: body ? "POST" : "GET",
|
|
291
|
+
headers: createInternalHandlerHeaders(originalRequest.headers),
|
|
292
|
+
signal: originalRequest.signal,
|
|
293
|
+
body
|
|
294
|
+
}),
|
|
295
|
+
params
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function interpolatePath(pathPattern, params) {
|
|
299
|
+
return pathPattern.replace(/:([A-Za-z0-9_]+)/g, (_, key) => {
|
|
300
|
+
const value = params[key];
|
|
301
|
+
if (value === void 0) throw new Error(`Missing required path param "${key}" for path "${pathPattern}".`);
|
|
302
|
+
return encodeURIComponent(value);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function shouldServeDocument(request, pathname) {
|
|
306
|
+
if (pathname.startsWith("/_litzjs/") || pathname.startsWith("/api/")) return false;
|
|
307
|
+
if (lastPathSegment(pathname).includes(".")) return false;
|
|
308
|
+
const accept = request.headers.get("accept") ?? "";
|
|
309
|
+
return accept.includes("text/html") || accept.includes("*/*");
|
|
310
|
+
}
|
|
311
|
+
function lastPathSegment(pathname) {
|
|
312
|
+
return trimPathSegments(pathname).at(-1) ?? "";
|
|
313
|
+
}
|
|
314
|
+
//#endregion
|
|
315
|
+
export { createServer };
|