what-server 0.11.0 → 0.11.1
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/dist/actions.min.js +0 -1
- package/dist/index.min.js +2 -3
- package/dist/islands.min.js +0 -1
- package/package.json +4 -4
- package/src/action-handler.js +10 -5
- package/src/adapter/core.js +4 -2
- package/dist/actions.js +0 -384
- package/dist/actions.js.map +0 -7
- package/dist/actions.min.js.map +0 -7
- package/dist/index.js +0 -1362
- package/dist/index.js.map +0 -7
- package/dist/index.min.js.map +0 -7
- package/dist/islands.js +0 -355
- package/dist/islands.js.map +0 -7
- package/dist/islands.min.js.map +0 -7
package/dist/index.js
DELETED
|
@@ -1,1362 +0,0 @@
|
|
|
1
|
-
// packages/server/src/index.js
|
|
2
|
-
import { h, runWithServerContext, beginHeadCollection, endHeadCollection } from "what-core";
|
|
3
|
-
|
|
4
|
-
// packages/server/src/serialize.js
|
|
5
|
-
var SCRIPT_UNSAFE = new RegExp("[<>&\\u2028\\u2029]", "g");
|
|
6
|
-
var ESCAPES = {
|
|
7
|
-
60: "\\u003c",
|
|
8
|
-
// <
|
|
9
|
-
62: "\\u003e",
|
|
10
|
-
// >
|
|
11
|
-
38: "\\u0026",
|
|
12
|
-
// &
|
|
13
|
-
8232: "\\u2028",
|
|
14
|
-
8233: "\\u2029"
|
|
15
|
-
};
|
|
16
|
-
function serializeState(value) {
|
|
17
|
-
return JSON.stringify(value).replace(SCRIPT_UNSAFE, (c) => ESCAPES[c.charCodeAt(0)]);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// packages/server/src/islands.js
|
|
21
|
-
import { mount, hydrate, signal, batch } from "what-core";
|
|
22
|
-
var sharedStores = /* @__PURE__ */ new Map();
|
|
23
|
-
function getIslandStoresSnapshot() {
|
|
24
|
-
const data = {};
|
|
25
|
-
for (const [name, store] of sharedStores) {
|
|
26
|
-
data[name] = store._getSnapshot();
|
|
27
|
-
}
|
|
28
|
-
return data;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// packages/server/src/actions.js
|
|
32
|
-
import { signal as signal2, batch as batch2 } from "what-core";
|
|
33
|
-
|
|
34
|
-
// packages/server/src/revalidation-registry.js
|
|
35
|
-
var _handler = null;
|
|
36
|
-
var isDev = typeof process !== "undefined" ? true : true;
|
|
37
|
-
function setRevalidationHandler(handler) {
|
|
38
|
-
_handler = handler;
|
|
39
|
-
}
|
|
40
|
-
function getRevalidationHandler() {
|
|
41
|
-
return _handler;
|
|
42
|
-
}
|
|
43
|
-
async function revalidatePath(path, options) {
|
|
44
|
-
if (_handler && _handler.revalidatePath) return _handler.revalidatePath(path, options);
|
|
45
|
-
if (isDev) {
|
|
46
|
-
console.warn(
|
|
47
|
-
`[what] revalidatePath('${path}') had no effect: no cache engine is bound. Create a what-isr engine and bind it in your adapter (setRevalidationHandler).`
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async function revalidateTag(tag, options) {
|
|
52
|
-
if (_handler && _handler.revalidateTag) return _handler.revalidateTag(tag, options);
|
|
53
|
-
if (isDev) {
|
|
54
|
-
console.warn(
|
|
55
|
-
`[what] revalidateTag('${tag}') had no effect: no cache engine is bound.`
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// packages/server/src/actions.js
|
|
61
|
-
var actionRegistry = /* @__PURE__ */ new Map();
|
|
62
|
-
function getCsrfToken() {
|
|
63
|
-
if (typeof document !== "undefined") {
|
|
64
|
-
const meta = document.querySelector('meta[name="what-csrf-token"]');
|
|
65
|
-
if (meta) {
|
|
66
|
-
return meta.getAttribute("content");
|
|
67
|
-
}
|
|
68
|
-
const match = document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);
|
|
69
|
-
if (match) {
|
|
70
|
-
return decodeURIComponent(match[1]);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
function generateCsrfToken() {
|
|
76
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
77
|
-
return crypto.randomUUID();
|
|
78
|
-
}
|
|
79
|
-
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
80
|
-
const arr = new Uint8Array(16);
|
|
81
|
-
crypto.getRandomValues(arr);
|
|
82
|
-
return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
83
|
-
}
|
|
84
|
-
throw new Error("[what] No secure random source available for CSRF token generation");
|
|
85
|
-
}
|
|
86
|
-
function validateCsrfToken(requestToken, sessionToken) {
|
|
87
|
-
if (!requestToken || !sessionToken) return false;
|
|
88
|
-
if (requestToken.length !== sessionToken.length) return false;
|
|
89
|
-
let result = 0;
|
|
90
|
-
for (let i = 0; i < requestToken.length; i++) {
|
|
91
|
-
result |= requestToken.charCodeAt(i) ^ sessionToken.charCodeAt(i);
|
|
92
|
-
}
|
|
93
|
-
return result === 0;
|
|
94
|
-
}
|
|
95
|
-
function csrfMetaTag(token) {
|
|
96
|
-
const escaped = String(token).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
97
|
-
return `<meta name="what-csrf-token" content="${escaped}">`;
|
|
98
|
-
}
|
|
99
|
-
var _actionCounter = 0;
|
|
100
|
-
function generateActionId() {
|
|
101
|
-
const rand = typeof crypto !== "undefined" && crypto.getRandomValues ? Array.from(crypto.getRandomValues(new Uint8Array(6)), (b) => b.toString(16).padStart(2, "0")).join("") : `c${(++_actionCounter).toString(36)}_${Date.now().toString(36)}`;
|
|
102
|
-
return `a_${rand}`;
|
|
103
|
-
}
|
|
104
|
-
function action(fn, options = {}) {
|
|
105
|
-
const id = options.id || generateActionId();
|
|
106
|
-
const { onError, onSuccess, revalidate } = options;
|
|
107
|
-
if (typeof window === "undefined") {
|
|
108
|
-
actionRegistry.set(id, { fn, options });
|
|
109
|
-
}
|
|
110
|
-
async function callAction(...args) {
|
|
111
|
-
if (typeof window === "undefined") {
|
|
112
|
-
return fn(...args);
|
|
113
|
-
}
|
|
114
|
-
const timeout = options.timeout || 3e4;
|
|
115
|
-
const controller = new AbortController();
|
|
116
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
117
|
-
try {
|
|
118
|
-
const csrfToken = getCsrfToken();
|
|
119
|
-
const headers = {
|
|
120
|
-
"Content-Type": "application/json",
|
|
121
|
-
"X-What-Action": id
|
|
122
|
-
};
|
|
123
|
-
if (csrfToken) headers["X-CSRF-Token"] = csrfToken;
|
|
124
|
-
const response = await fetch("/__what_action", {
|
|
125
|
-
method: "POST",
|
|
126
|
-
headers,
|
|
127
|
-
credentials: "same-origin",
|
|
128
|
-
signal: controller.signal,
|
|
129
|
-
body: JSON.stringify({ args })
|
|
130
|
-
});
|
|
131
|
-
if (!response.ok) {
|
|
132
|
-
const error = await response.json().catch(() => ({ message: "Action failed" }));
|
|
133
|
-
throw new Error(error.message || "Action failed");
|
|
134
|
-
}
|
|
135
|
-
const result = await response.json();
|
|
136
|
-
if (onSuccess) onSuccess(result);
|
|
137
|
-
if (revalidate) {
|
|
138
|
-
for (const path of revalidate) {
|
|
139
|
-
invalidatePath(path);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return result;
|
|
143
|
-
} catch (error) {
|
|
144
|
-
if (error.name === "AbortError") {
|
|
145
|
-
const timeoutError = new Error(`Action "${id}" timed out after ${timeout}ms`);
|
|
146
|
-
timeoutError.code = "TIMEOUT";
|
|
147
|
-
if (onError) onError(timeoutError);
|
|
148
|
-
throw timeoutError;
|
|
149
|
-
}
|
|
150
|
-
if (onError) onError(error);
|
|
151
|
-
throw error;
|
|
152
|
-
} finally {
|
|
153
|
-
clearTimeout(timeoutId);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
callAction._actionId = id;
|
|
157
|
-
callAction._isAction = true;
|
|
158
|
-
return callAction;
|
|
159
|
-
}
|
|
160
|
-
function formAction(actionFn, options = {}) {
|
|
161
|
-
const { onSuccess, onError, resetOnSuccess = true } = options;
|
|
162
|
-
return async (formDataOrEvent) => {
|
|
163
|
-
let formData;
|
|
164
|
-
let form;
|
|
165
|
-
if (formDataOrEvent instanceof Event) {
|
|
166
|
-
formDataOrEvent.preventDefault();
|
|
167
|
-
form = formDataOrEvent.target;
|
|
168
|
-
formData = new FormData(form);
|
|
169
|
-
} else {
|
|
170
|
-
formData = formDataOrEvent;
|
|
171
|
-
}
|
|
172
|
-
const data = {};
|
|
173
|
-
let hasFiles = false;
|
|
174
|
-
for (const [key, value] of formData.entries()) {
|
|
175
|
-
if (typeof File !== "undefined" && value instanceof File) {
|
|
176
|
-
hasFiles = true;
|
|
177
|
-
}
|
|
178
|
-
if (data[key]) {
|
|
179
|
-
if (Array.isArray(data[key])) {
|
|
180
|
-
data[key].push(value);
|
|
181
|
-
} else {
|
|
182
|
-
data[key] = [data[key], value];
|
|
183
|
-
}
|
|
184
|
-
} else {
|
|
185
|
-
data[key] = value;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
try {
|
|
189
|
-
const result = hasFiles ? await actionFn(data, formData) : await actionFn(data);
|
|
190
|
-
if (onSuccess) onSuccess(result, form);
|
|
191
|
-
if (resetOnSuccess && form) form.reset();
|
|
192
|
-
return result;
|
|
193
|
-
} catch (error) {
|
|
194
|
-
if (onError) onError(error, form);
|
|
195
|
-
throw error;
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
function useAction(actionFn) {
|
|
200
|
-
const isPending = signal2(false);
|
|
201
|
-
const error = signal2(null);
|
|
202
|
-
const data = signal2(null);
|
|
203
|
-
async function trigger(...args) {
|
|
204
|
-
isPending.set(true);
|
|
205
|
-
error.set(null);
|
|
206
|
-
try {
|
|
207
|
-
const result = await actionFn(...args);
|
|
208
|
-
data.set(result);
|
|
209
|
-
return result;
|
|
210
|
-
} catch (e) {
|
|
211
|
-
error.set(e);
|
|
212
|
-
throw e;
|
|
213
|
-
} finally {
|
|
214
|
-
isPending.set(false);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
trigger,
|
|
219
|
-
isPending: () => isPending(),
|
|
220
|
-
error: () => error(),
|
|
221
|
-
data: () => data(),
|
|
222
|
-
reset: () => {
|
|
223
|
-
error.set(null);
|
|
224
|
-
data.set(null);
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
function useFormAction(actionFn, options = {}) {
|
|
229
|
-
const { resetOnSuccess = true } = options;
|
|
230
|
-
const formRef = { current: null };
|
|
231
|
-
const actionState = useAction(formAction(actionFn, { resetOnSuccess }));
|
|
232
|
-
function handleSubmit(e) {
|
|
233
|
-
e.preventDefault();
|
|
234
|
-
const formData = new FormData(e.target);
|
|
235
|
-
formRef.current = e.target;
|
|
236
|
-
return actionState.trigger(formData);
|
|
237
|
-
}
|
|
238
|
-
return {
|
|
239
|
-
...actionState,
|
|
240
|
-
handleSubmit,
|
|
241
|
-
formRef
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
function useOptimistic(initialValue, reducer) {
|
|
245
|
-
const value = signal2(initialValue);
|
|
246
|
-
const pending = signal2([]);
|
|
247
|
-
const baseValue = signal2(initialValue);
|
|
248
|
-
function addOptimistic(action2) {
|
|
249
|
-
const optimisticValue = reducer(value.peek(), action2);
|
|
250
|
-
batch2(() => {
|
|
251
|
-
pending.set([...pending.peek(), action2]);
|
|
252
|
-
value.set(optimisticValue);
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
function resolve(action2, serverValue) {
|
|
256
|
-
batch2(() => {
|
|
257
|
-
pending.set(pending.peek().filter((a) => a !== action2));
|
|
258
|
-
if (serverValue !== void 0) {
|
|
259
|
-
baseValue.set(serverValue);
|
|
260
|
-
let current = serverValue;
|
|
261
|
-
for (const a of pending.peek()) {
|
|
262
|
-
current = reducer(current, a);
|
|
263
|
-
}
|
|
264
|
-
value.set(current);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
function rollback(action2, realValue) {
|
|
269
|
-
batch2(() => {
|
|
270
|
-
const newPending = pending.peek().filter((a) => a !== action2);
|
|
271
|
-
pending.set(newPending);
|
|
272
|
-
const base = realValue !== void 0 ? realValue : baseValue.peek();
|
|
273
|
-
baseValue.set(base);
|
|
274
|
-
let current = base;
|
|
275
|
-
for (const a of newPending) {
|
|
276
|
-
current = reducer(current, a);
|
|
277
|
-
}
|
|
278
|
-
value.set(current);
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
async function withOptimistic(action2, asyncFn) {
|
|
282
|
-
addOptimistic(action2);
|
|
283
|
-
try {
|
|
284
|
-
const result = await asyncFn();
|
|
285
|
-
resolve(action2, result);
|
|
286
|
-
return result;
|
|
287
|
-
} catch (e) {
|
|
288
|
-
rollback(action2);
|
|
289
|
-
throw e;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return {
|
|
293
|
-
value: () => value(),
|
|
294
|
-
isPending: () => pending().length > 0,
|
|
295
|
-
addOptimistic,
|
|
296
|
-
resolve,
|
|
297
|
-
rollback,
|
|
298
|
-
withOptimistic,
|
|
299
|
-
set: (v) => {
|
|
300
|
-
value.set(v);
|
|
301
|
-
baseValue.set(v);
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
var revalidationCallbacks = /* @__PURE__ */ new Map();
|
|
306
|
-
function onRevalidate(path, callback) {
|
|
307
|
-
if (!revalidationCallbacks.has(path)) {
|
|
308
|
-
revalidationCallbacks.set(path, /* @__PURE__ */ new Set());
|
|
309
|
-
}
|
|
310
|
-
revalidationCallbacks.get(path).add(callback);
|
|
311
|
-
return () => {
|
|
312
|
-
revalidationCallbacks.get(path)?.delete(callback);
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
function invalidatePath(path) {
|
|
316
|
-
const callbacks = revalidationCallbacks.get(path);
|
|
317
|
-
if (callbacks) {
|
|
318
|
-
for (const cb of callbacks) {
|
|
319
|
-
try {
|
|
320
|
-
cb();
|
|
321
|
-
} catch (e) {
|
|
322
|
-
console.error("[what] Revalidation error:", e);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
function handleActionRequest(req, actionId, args, options = {}) {
|
|
328
|
-
const { csrfToken: sessionCsrfToken, skipCsrf = false } = options;
|
|
329
|
-
if (!skipCsrf) {
|
|
330
|
-
if (!sessionCsrfToken) {
|
|
331
|
-
return Promise.resolve({
|
|
332
|
-
status: 500,
|
|
333
|
-
body: {
|
|
334
|
-
message: "[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
const requestCsrfToken = req?.headers?.["x-csrf-token"] || req?.headers?.["X-CSRF-Token"];
|
|
339
|
-
if (!validateCsrfToken(requestCsrfToken, sessionCsrfToken)) {
|
|
340
|
-
return Promise.resolve({ status: 403, body: { message: "Invalid CSRF token" } });
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
const action2 = actionRegistry.get(actionId);
|
|
344
|
-
if (!action2) {
|
|
345
|
-
return Promise.resolve({ status: 404, body: { message: "Action not found" } });
|
|
346
|
-
}
|
|
347
|
-
if (!Array.isArray(args)) {
|
|
348
|
-
return Promise.resolve({ status: 400, body: { message: "Invalid action arguments" } });
|
|
349
|
-
}
|
|
350
|
-
return action2.fn(...args).then(async (result) => {
|
|
351
|
-
const opts = action2.options || {};
|
|
352
|
-
if (Array.isArray(opts.revalidate)) {
|
|
353
|
-
for (const p of opts.revalidate) await revalidatePath(p);
|
|
354
|
-
}
|
|
355
|
-
if (Array.isArray(opts.revalidateTags)) {
|
|
356
|
-
for (const t of opts.revalidateTags) await revalidateTag(t);
|
|
357
|
-
}
|
|
358
|
-
return { status: 200, body: result };
|
|
359
|
-
}).catch((error) => {
|
|
360
|
-
console.error(`[what] Action "${actionId}" error:`, error);
|
|
361
|
-
return {
|
|
362
|
-
status: 500,
|
|
363
|
-
body: { message: "Action failed" }
|
|
364
|
-
};
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
function getRegisteredActions() {
|
|
368
|
-
return [...actionRegistry.keys()];
|
|
369
|
-
}
|
|
370
|
-
function useMutation(mutationFn, options = {}) {
|
|
371
|
-
const { onSuccess, onError, onSettled } = options;
|
|
372
|
-
const state = {
|
|
373
|
-
isPending: signal2(false),
|
|
374
|
-
error: signal2(null),
|
|
375
|
-
data: signal2(null)
|
|
376
|
-
};
|
|
377
|
-
async function mutate(...args) {
|
|
378
|
-
state.isPending.set(true);
|
|
379
|
-
state.error.set(null);
|
|
380
|
-
try {
|
|
381
|
-
const result = await mutationFn(...args);
|
|
382
|
-
state.data.set(result);
|
|
383
|
-
if (onSuccess) onSuccess(result, ...args);
|
|
384
|
-
return result;
|
|
385
|
-
} catch (error) {
|
|
386
|
-
state.error.set(error);
|
|
387
|
-
if (onError) onError(error, ...args);
|
|
388
|
-
throw error;
|
|
389
|
-
} finally {
|
|
390
|
-
state.isPending.set(false);
|
|
391
|
-
if (onSettled) onSettled(state.data.peek(), state.error.peek(), ...args);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return {
|
|
395
|
-
mutate,
|
|
396
|
-
isPending: () => state.isPending(),
|
|
397
|
-
error: () => state.error(),
|
|
398
|
-
data: () => state.data(),
|
|
399
|
-
reset: () => {
|
|
400
|
-
state.error.set(null);
|
|
401
|
-
state.data.set(null);
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// packages/server/src/action-handler.js
|
|
407
|
-
var DEFAULT_BASE_PATH = "/__what_action";
|
|
408
|
-
var MAX_BODY_BYTES = 1024 * 1024;
|
|
409
|
-
function lowerHeaders(headers) {
|
|
410
|
-
if (!headers) return {};
|
|
411
|
-
if (typeof headers.forEach === "function" && typeof headers.get === "function") {
|
|
412
|
-
const out2 = {};
|
|
413
|
-
headers.forEach((v, k) => {
|
|
414
|
-
out2[k.toLowerCase()] = v;
|
|
415
|
-
});
|
|
416
|
-
return out2;
|
|
417
|
-
}
|
|
418
|
-
const out = {};
|
|
419
|
-
for (const k in headers) out[k.toLowerCase()] = headers[k];
|
|
420
|
-
return out;
|
|
421
|
-
}
|
|
422
|
-
function jsonResponse(status, bodyObj) {
|
|
423
|
-
return {
|
|
424
|
-
status,
|
|
425
|
-
headers: { "content-type": "application/json" },
|
|
426
|
-
body: JSON.stringify(bodyObj)
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
function htmlResponse(status, message) {
|
|
430
|
-
return {
|
|
431
|
-
status,
|
|
432
|
-
headers: { "content-type": "text/html; charset=utf-8" },
|
|
433
|
-
body: `<!DOCTYPE html><html><body><h1>${status}</h1><p>${String(message).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")}</p></body></html>`
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
function safeLocalPath(value) {
|
|
437
|
-
if (typeof value !== "string" || !value.startsWith("/")) return null;
|
|
438
|
-
if (/^[/\\]{2}/.test(value) || value.includes("\\")) return null;
|
|
439
|
-
try {
|
|
440
|
-
const u = new URL(value, "http://localhost");
|
|
441
|
-
if (u.origin !== "http://localhost") return null;
|
|
442
|
-
return u.pathname + u.search;
|
|
443
|
-
} catch {
|
|
444
|
-
return null;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
function safeRedirectTarget(form, headers) {
|
|
448
|
-
const explicit = safeLocalPath(form && form._redirect);
|
|
449
|
-
if (explicit) return explicit;
|
|
450
|
-
const referer = headers.referer || headers.referrer;
|
|
451
|
-
if (referer) {
|
|
452
|
-
try {
|
|
453
|
-
const u = new URL(referer, "http://localhost");
|
|
454
|
-
const path = safeLocalPath(u.pathname + u.search);
|
|
455
|
-
if (path) return path;
|
|
456
|
-
} catch {
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
return "/";
|
|
460
|
-
}
|
|
461
|
-
var RESERVED_FORM_FIELDS = /* @__PURE__ */ new Set(["_action", "data-action", "_csrf", "_redirect"]);
|
|
462
|
-
function createActionHandler(options = {}) {
|
|
463
|
-
const { getCsrfToken: getCsrfToken2, skipCsrf = false } = options;
|
|
464
|
-
return async function handle(reqLike) {
|
|
465
|
-
const method = (reqLike.method || "POST").toUpperCase();
|
|
466
|
-
if (method !== "POST") {
|
|
467
|
-
return jsonResponse(405, { message: "Method Not Allowed" });
|
|
468
|
-
}
|
|
469
|
-
const headers = lowerHeaders(reqLike.headers);
|
|
470
|
-
const headerActionId = headers["x-what-action"];
|
|
471
|
-
const contentType = headers["content-type"] || "";
|
|
472
|
-
const isFormPost = !headerActionId && contentType.includes("application/x-www-form-urlencoded");
|
|
473
|
-
const sessionCsrfToken = skipCsrf ? void 0 : getCsrfToken2 ? await getCsrfToken2(reqLike) : void 0;
|
|
474
|
-
if (isFormPost) {
|
|
475
|
-
const form = reqLike.body || {};
|
|
476
|
-
const actionId = form._action || form["data-action"] || reqLike.query && reqLike.query.action;
|
|
477
|
-
if (!actionId) {
|
|
478
|
-
return htmlResponse(400, 'Missing action name (add a hidden "_action" field or ?action= query param)');
|
|
479
|
-
}
|
|
480
|
-
const formHeaders = { ...headers };
|
|
481
|
-
if (form._csrf && !formHeaders["x-csrf-token"]) formHeaders["x-csrf-token"] = String(form._csrf);
|
|
482
|
-
if (!skipCsrf && getCsrfToken2 && !sessionCsrfToken) {
|
|
483
|
-
return htmlResponse(403, "Missing CSRF token");
|
|
484
|
-
}
|
|
485
|
-
const data = {};
|
|
486
|
-
for (const [k, v] of Object.entries(form)) {
|
|
487
|
-
if (!RESERVED_FORM_FIELDS.has(k)) data[k] = v;
|
|
488
|
-
}
|
|
489
|
-
const result2 = await handleActionRequest(
|
|
490
|
-
{ headers: formHeaders },
|
|
491
|
-
actionId,
|
|
492
|
-
[data],
|
|
493
|
-
{ csrfToken: sessionCsrfToken, skipCsrf }
|
|
494
|
-
);
|
|
495
|
-
if (result2.status === 200) {
|
|
496
|
-
return {
|
|
497
|
-
status: 303,
|
|
498
|
-
headers: { location: safeRedirectTarget(form, headers) },
|
|
499
|
-
body: ""
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
return htmlResponse(result2.status, result2.body && result2.body.message || "Action failed");
|
|
503
|
-
}
|
|
504
|
-
if (!headerActionId) {
|
|
505
|
-
return jsonResponse(400, { message: "Missing X-What-Action header" });
|
|
506
|
-
}
|
|
507
|
-
if (!skipCsrf && getCsrfToken2 && !sessionCsrfToken) {
|
|
508
|
-
return jsonResponse(403, { message: "Missing CSRF token" });
|
|
509
|
-
}
|
|
510
|
-
const body = reqLike.body || {};
|
|
511
|
-
const args = body.args;
|
|
512
|
-
const result = await handleActionRequest(
|
|
513
|
-
{ headers },
|
|
514
|
-
headerActionId,
|
|
515
|
-
args,
|
|
516
|
-
{ csrfToken: sessionCsrfToken, skipCsrf }
|
|
517
|
-
);
|
|
518
|
-
return jsonResponse(result.status, result.body);
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
function nodeActionMiddleware(options = {}) {
|
|
522
|
-
const basePath = options.basePath || DEFAULT_BASE_PATH;
|
|
523
|
-
const handle = createActionHandler(options);
|
|
524
|
-
return async function middleware(req, res, next) {
|
|
525
|
-
const [url, search] = (req.url || "").split("?");
|
|
526
|
-
if (url !== basePath || (req.method || "").toUpperCase() !== "POST") {
|
|
527
|
-
return next ? next() : void 0;
|
|
528
|
-
}
|
|
529
|
-
let body;
|
|
530
|
-
try {
|
|
531
|
-
const raw = await readRawBody(req);
|
|
532
|
-
body = parseActionBody(raw, req.headers["content-type"] || "");
|
|
533
|
-
} catch (err) {
|
|
534
|
-
res.writeHead(err.code === "BODY_TOO_LARGE" ? 413 : 400, { "content-type": "application/json" });
|
|
535
|
-
res.end(JSON.stringify({ message: err.code === "BODY_TOO_LARGE" ? "Payload too large" : "Invalid request body" }));
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
const query = Object.fromEntries(new URLSearchParams(search || ""));
|
|
539
|
-
const out = await handle({ method: req.method, headers: req.headers, body, query });
|
|
540
|
-
res.writeHead(out.status, out.headers);
|
|
541
|
-
res.end(out.body);
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
function parseActionBody(raw, contentType) {
|
|
545
|
-
if ((contentType || "").includes("application/x-www-form-urlencoded")) {
|
|
546
|
-
const fields = {};
|
|
547
|
-
for (const [k, v] of new URLSearchParams(String(raw))) {
|
|
548
|
-
if (fields[k] === void 0) fields[k] = v;
|
|
549
|
-
else if (Array.isArray(fields[k])) fields[k].push(v);
|
|
550
|
-
else fields[k] = [fields[k], v];
|
|
551
|
-
}
|
|
552
|
-
return fields;
|
|
553
|
-
}
|
|
554
|
-
if (raw == null || raw === "") return {};
|
|
555
|
-
return JSON.parse(String(raw));
|
|
556
|
-
}
|
|
557
|
-
async function readFetchBodyCapped(request, limit = MAX_BODY_BYTES) {
|
|
558
|
-
const declared = Number(request.headers.get("content-length"));
|
|
559
|
-
if (Number.isFinite(declared) && declared > limit) {
|
|
560
|
-
return { tooLarge: true };
|
|
561
|
-
}
|
|
562
|
-
const body = request.body;
|
|
563
|
-
if (!body || typeof body.getReader !== "function") {
|
|
564
|
-
const raw = await request.text();
|
|
565
|
-
if (Buffer.byteLength(raw, "utf8") > limit) return { tooLarge: true };
|
|
566
|
-
return { raw };
|
|
567
|
-
}
|
|
568
|
-
const reader = body.getReader();
|
|
569
|
-
const chunks = [];
|
|
570
|
-
let size = 0;
|
|
571
|
-
while (true) {
|
|
572
|
-
const { done, value } = await reader.read();
|
|
573
|
-
if (done) break;
|
|
574
|
-
if (value) {
|
|
575
|
-
size += value.byteLength;
|
|
576
|
-
if (size > limit) {
|
|
577
|
-
try {
|
|
578
|
-
await reader.cancel();
|
|
579
|
-
} catch {
|
|
580
|
-
}
|
|
581
|
-
return { tooLarge: true };
|
|
582
|
-
}
|
|
583
|
-
chunks.push(value);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
return { raw: Buffer.concat(chunks.map((c) => Buffer.from(c))).toString("utf8") };
|
|
587
|
-
}
|
|
588
|
-
function readRawBody(req) {
|
|
589
|
-
return new Promise((resolve, reject) => {
|
|
590
|
-
let size = 0;
|
|
591
|
-
const chunks = [];
|
|
592
|
-
req.on("data", (chunk) => {
|
|
593
|
-
size += chunk.length;
|
|
594
|
-
if (size > MAX_BODY_BYTES) {
|
|
595
|
-
const e = new Error("Body too large");
|
|
596
|
-
e.code = "BODY_TOO_LARGE";
|
|
597
|
-
reject(e);
|
|
598
|
-
req.destroy?.();
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
chunks.push(chunk);
|
|
602
|
-
});
|
|
603
|
-
req.on("end", () => {
|
|
604
|
-
if (chunks.length === 0) return resolve("");
|
|
605
|
-
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
606
|
-
});
|
|
607
|
-
req.on("error", reject);
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
function fetchActionHandler(options = {}) {
|
|
611
|
-
const handle = createActionHandler(options);
|
|
612
|
-
return async function(request) {
|
|
613
|
-
let body = {};
|
|
614
|
-
try {
|
|
615
|
-
const read = await readFetchBodyCapped(request);
|
|
616
|
-
if (read.tooLarge) {
|
|
617
|
-
return new Response(JSON.stringify({ message: "Payload too large" }), {
|
|
618
|
-
status: 413,
|
|
619
|
-
headers: { "content-type": "application/json" }
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
body = parseActionBody(read.raw, request.headers.get("content-type") || "");
|
|
623
|
-
} catch {
|
|
624
|
-
body = {};
|
|
625
|
-
}
|
|
626
|
-
let query = {};
|
|
627
|
-
try {
|
|
628
|
-
query = Object.fromEntries(new URL(request.url, "http://localhost").searchParams);
|
|
629
|
-
} catch {
|
|
630
|
-
}
|
|
631
|
-
const out = await handle({ method: request.method, headers: request.headers, body, query });
|
|
632
|
-
return new Response(out.body, { status: out.status, headers: out.headers });
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// packages/server/src/adapter/core.js
|
|
637
|
-
import { matchRoute, parseQuery } from "what-router/match";
|
|
638
|
-
var ACTION_PATH = "/__what_action";
|
|
639
|
-
var REVALIDATE_PATH = "/__what_revalidate";
|
|
640
|
-
var CSRF_COOKIE = "what-csrf";
|
|
641
|
-
function headersToObject(headers) {
|
|
642
|
-
const out = {};
|
|
643
|
-
if (headers && typeof headers.forEach === "function") headers.forEach((v, k) => {
|
|
644
|
-
out[k.toLowerCase()] = v;
|
|
645
|
-
});
|
|
646
|
-
return out;
|
|
647
|
-
}
|
|
648
|
-
function readCookie(cookieHeader, name) {
|
|
649
|
-
if (!cookieHeader) return null;
|
|
650
|
-
const match = String(cookieHeader).match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
|
|
651
|
-
return match ? decodeURIComponent(match[1]) : null;
|
|
652
|
-
}
|
|
653
|
-
async function readActionBody(request) {
|
|
654
|
-
try {
|
|
655
|
-
const read = await readFetchBodyCapped(request);
|
|
656
|
-
if (read.tooLarge) return { tooLarge: true };
|
|
657
|
-
return parseActionBody(read.raw, request.headers.get("content-type") || "");
|
|
658
|
-
} catch {
|
|
659
|
-
return {};
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
async function readJsonBody(request) {
|
|
663
|
-
try {
|
|
664
|
-
return await request.json();
|
|
665
|
-
} catch {
|
|
666
|
-
return {};
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
function defaultRenderRoute(documentOptions) {
|
|
670
|
-
return async function renderRoute(routeMatch) {
|
|
671
|
-
const { route, params, query, request } = routeMatch;
|
|
672
|
-
const pageModule = { default: route.component, loader: route.loader };
|
|
673
|
-
const opts = routeMatch.csrfToken ? { ...documentOptions, csrfToken: routeMatch.csrfToken } : documentOptions;
|
|
674
|
-
const html = await renderDocument(pageModule, { params, query, request }, opts);
|
|
675
|
-
return {
|
|
676
|
-
html,
|
|
677
|
-
status: 200,
|
|
678
|
-
tags: routeMatch.config && routeMatch.config.tags || [],
|
|
679
|
-
path: routeMatch.path
|
|
680
|
-
};
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
function createRequestHandler(options = {}) {
|
|
684
|
-
const {
|
|
685
|
-
routes = [],
|
|
686
|
-
cache,
|
|
687
|
-
render,
|
|
688
|
-
revalidateWebhook,
|
|
689
|
-
document: documentOptions = {},
|
|
690
|
-
notFound,
|
|
691
|
-
basePath = "",
|
|
692
|
-
csrf = true
|
|
693
|
-
} = options;
|
|
694
|
-
const autoCsrf = csrf !== false && !options.actionHandler;
|
|
695
|
-
const actionHandler = options.actionHandler || createActionHandler(
|
|
696
|
-
autoCsrf ? { getCsrfToken: (reqLike) => readCookie(reqLike.headers && reqLike.headers.cookie, CSRF_COOKIE) } : { skipCsrf: true }
|
|
697
|
-
);
|
|
698
|
-
const renderRoute = render || defaultRenderRoute(documentOptions);
|
|
699
|
-
if (cache && (cache.revalidatePath || cache.revalidateTag)) {
|
|
700
|
-
setRevalidationHandler({
|
|
701
|
-
revalidatePath: cache.revalidatePath,
|
|
702
|
-
revalidateTag: cache.revalidateTag
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
return async function handle(request) {
|
|
706
|
-
const url = new URL(request.url, "http://localhost");
|
|
707
|
-
let pathname = url.pathname;
|
|
708
|
-
if (basePath && pathname.startsWith(basePath)) pathname = pathname.slice(basePath.length) || "/";
|
|
709
|
-
if (request.method === "POST" && pathname === ACTION_PATH) {
|
|
710
|
-
const body = await readActionBody(request);
|
|
711
|
-
if (body && body.tooLarge) {
|
|
712
|
-
return new Response(JSON.stringify({ message: "Payload too large" }), {
|
|
713
|
-
status: 413,
|
|
714
|
-
headers: { "content-type": "application/json" }
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
const out2 = await actionHandler({
|
|
718
|
-
method: "POST",
|
|
719
|
-
headers: headersToObject(request.headers),
|
|
720
|
-
body,
|
|
721
|
-
query: Object.fromEntries(url.searchParams)
|
|
722
|
-
});
|
|
723
|
-
return new Response(out2.body, { status: out2.status, headers: out2.headers });
|
|
724
|
-
}
|
|
725
|
-
let csrfToken = null;
|
|
726
|
-
let csrfSetCookie = null;
|
|
727
|
-
if (autoCsrf) {
|
|
728
|
-
csrfToken = readCookie(headersToObject(request.headers).cookie, CSRF_COOKIE);
|
|
729
|
-
if (!csrfToken) {
|
|
730
|
-
csrfToken = generateCsrfToken();
|
|
731
|
-
const reqHeaders = headersToObject(request.headers);
|
|
732
|
-
const isHttps = reqHeaders["x-forwarded-proto"] === "https" || url.protocol === "https:" || false;
|
|
733
|
-
csrfSetCookie = `${CSRF_COOKIE}=${encodeURIComponent(csrfToken)}; Path=/; SameSite=Lax` + (isHttps ? "; Secure" : "");
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
const withCsrfCookie = (headers2) => {
|
|
737
|
-
if (csrfSetCookie) headers2["set-cookie"] = csrfSetCookie;
|
|
738
|
-
return headers2;
|
|
739
|
-
};
|
|
740
|
-
if (request.method === "POST" && pathname === REVALIDATE_PATH && revalidateWebhook) {
|
|
741
|
-
const body = await readJsonBody(request);
|
|
742
|
-
const out2 = await revalidateWebhook({ headers: headersToObject(request.headers), body });
|
|
743
|
-
return new Response(JSON.stringify(out2.body), {
|
|
744
|
-
status: out2.status,
|
|
745
|
-
headers: { "content-type": "application/json" }
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
const matched = matchRoute(pathname, routes);
|
|
749
|
-
if (!matched) {
|
|
750
|
-
const html = notFound ? notFound() : "<!DOCTYPE html><html><body><h1>404 \u2014 Not Found</h1></body></html>";
|
|
751
|
-
return new Response(html, { status: 404, headers: withCsrfCookie({ "content-type": "text/html; charset=utf-8" }) });
|
|
752
|
-
}
|
|
753
|
-
const { route, params } = matched;
|
|
754
|
-
const config = route.page || { mode: route.mode || "client" };
|
|
755
|
-
const routeMatch = { path: pathname, query: parseQuery(url.search), config, route, params, request };
|
|
756
|
-
if (cache && config.mode !== "server") {
|
|
757
|
-
const result = await cache.handle(routeMatch, () => renderRoute(routeMatch));
|
|
758
|
-
return new Response(result.html, {
|
|
759
|
-
status: result.status || 200,
|
|
760
|
-
headers: withCsrfCookie({ "content-type": "text/html; charset=utf-8", ...result.headers || {} })
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
if (csrfToken) routeMatch.csrfToken = csrfToken;
|
|
764
|
-
const out = await renderRoute(routeMatch);
|
|
765
|
-
const headers = withCsrfCookie({ "content-type": "text/html; charset=utf-8" });
|
|
766
|
-
if (config.mode === "server") headers["Cache-Control"] = "private, no-store";
|
|
767
|
-
return new Response(out.html, { status: out.status || 200, headers });
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// packages/server/src/adapter/node.js
|
|
772
|
-
import http from "node:http";
|
|
773
|
-
async function nodeToWebRequest(req) {
|
|
774
|
-
const host = req.headers.host || "localhost";
|
|
775
|
-
const url = `http://${host}${req.url}`;
|
|
776
|
-
const headers = new Headers();
|
|
777
|
-
for (const [k, v] of Object.entries(req.headers)) {
|
|
778
|
-
if (v != null) headers.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
779
|
-
}
|
|
780
|
-
let body;
|
|
781
|
-
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
782
|
-
const chunks = [];
|
|
783
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
784
|
-
if (chunks.length) body = Buffer.concat(chunks);
|
|
785
|
-
}
|
|
786
|
-
return new Request(url, { method: req.method, headers, body });
|
|
787
|
-
}
|
|
788
|
-
async function sendWebResponse(res, webRes) {
|
|
789
|
-
res.statusCode = webRes.status;
|
|
790
|
-
webRes.headers.forEach((value, key) => res.setHeader(key, value));
|
|
791
|
-
const text = await webRes.text();
|
|
792
|
-
res.end(text);
|
|
793
|
-
}
|
|
794
|
-
function toNodeListener(handler) {
|
|
795
|
-
return async function listener(req, res) {
|
|
796
|
-
try {
|
|
797
|
-
const webReq = await nodeToWebRequest(req);
|
|
798
|
-
const webRes = await handler(webReq);
|
|
799
|
-
await sendWebResponse(res, webRes);
|
|
800
|
-
} catch (err) {
|
|
801
|
-
if (!res.headersSent) res.writeHead(500, { "content-type": "text/html; charset=utf-8" });
|
|
802
|
-
res.end("<!DOCTYPE html><html><body><h1>500 \u2014 Server Error</h1></body></html>");
|
|
803
|
-
console.error("[what-server] request error:", err);
|
|
804
|
-
}
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
function whatMiddleware(options = {}) {
|
|
808
|
-
const handler = createRequestHandler(options);
|
|
809
|
-
return async function middleware(req, res, next) {
|
|
810
|
-
const webReq = await nodeToWebRequest(req);
|
|
811
|
-
const webRes = await handler(webReq);
|
|
812
|
-
if (webRes.status === 404 && typeof next === "function") return next();
|
|
813
|
-
await sendWebResponse(res, webRes);
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
function createServer(options = {}) {
|
|
817
|
-
const handler = createRequestHandler(options);
|
|
818
|
-
const server2 = http.createServer(toNodeListener(handler));
|
|
819
|
-
const { scheduler } = options;
|
|
820
|
-
if (scheduler) {
|
|
821
|
-
scheduler.start();
|
|
822
|
-
const stop = () => {
|
|
823
|
-
try {
|
|
824
|
-
scheduler.stop();
|
|
825
|
-
} catch {
|
|
826
|
-
}
|
|
827
|
-
server2.close();
|
|
828
|
-
};
|
|
829
|
-
process.once("SIGTERM", stop);
|
|
830
|
-
process.once("SIGINT", stop);
|
|
831
|
-
}
|
|
832
|
-
return server2;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// packages/server/src/adapter/static.js
|
|
836
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
837
|
-
import { join } from "node:path";
|
|
838
|
-
import { matchRoute as matchRoute2 } from "what-router/match";
|
|
839
|
-
function isDynamic(path) {
|
|
840
|
-
return path.includes(":") || path.includes("*") || path.includes("[");
|
|
841
|
-
}
|
|
842
|
-
function buildConcretePath(pattern, params) {
|
|
843
|
-
return pattern.replace(/\[\.\.\.(\w+)\]/g, (_, n) => params[n] ?? "").replace(/\[(\w+)\]/g, (_, n) => params[n] ?? "").replace(/[:*](\w+)/g, (_, n) => params[n] ?? "");
|
|
844
|
-
}
|
|
845
|
-
async function exportStatic({ routes = [], outDir, render, documentOptions = {} } = {}) {
|
|
846
|
-
const written = [];
|
|
847
|
-
for (const route of routes) {
|
|
848
|
-
const mode = route.page && route.page.mode || route.mode;
|
|
849
|
-
if (mode !== "static" && mode !== "hybrid") continue;
|
|
850
|
-
const pageModule = { default: route.component, loader: route.loader };
|
|
851
|
-
let concrete = [route.path];
|
|
852
|
-
if (isDynamic(route.path)) {
|
|
853
|
-
if (typeof route.getStaticPaths !== "function") continue;
|
|
854
|
-
const result = await route.getStaticPaths();
|
|
855
|
-
concrete = (result.paths || []).map((p) => buildConcretePath(route.path, p.params || {}));
|
|
856
|
-
}
|
|
857
|
-
for (const urlPath of concrete) {
|
|
858
|
-
const matched = matchRoute2(urlPath, [route]);
|
|
859
|
-
const params = matched ? matched.params : {};
|
|
860
|
-
const reqCtx = { params, query: {} };
|
|
861
|
-
const html = render ? await render(pageModule, reqCtx) : await renderDocument(pageModule, reqCtx, documentOptions);
|
|
862
|
-
const dirPath = join(outDir, urlPath === "/" ? "" : urlPath);
|
|
863
|
-
await mkdir(dirPath, { recursive: true });
|
|
864
|
-
await writeFile(join(dirPath, "index.html"), html);
|
|
865
|
-
if (typeof route.loader === "function") {
|
|
866
|
-
const data = await route.loader(reqCtx);
|
|
867
|
-
await writeFile(join(dirPath, "__what_data.json"), serializeState({ loaderData: data }));
|
|
868
|
-
}
|
|
869
|
-
written.push(urlPath);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
return { pages: written };
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// packages/server/src/adapter/cloudflare.js
|
|
876
|
-
function createCloudflareHandler(options = {}) {
|
|
877
|
-
const handle = createRequestHandler(options);
|
|
878
|
-
return {
|
|
879
|
-
async fetch(request, env, ctx) {
|
|
880
|
-
if (env) request.__env = env;
|
|
881
|
-
if (ctx) request.__ctx = ctx;
|
|
882
|
-
return handle(request);
|
|
883
|
-
}
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// packages/server/src/adapter/vercel.js
|
|
888
|
-
function createVercelHandler(options = {}) {
|
|
889
|
-
return createRequestHandler(options);
|
|
890
|
-
}
|
|
891
|
-
async function buildVercelOutput({
|
|
892
|
-
outDir = ".vercel/output",
|
|
893
|
-
functionName = "render",
|
|
894
|
-
runtime = "nodejs22.x",
|
|
895
|
-
files = null,
|
|
896
|
-
handler = "index.mjs",
|
|
897
|
-
staticDir = null
|
|
898
|
-
} = {}) {
|
|
899
|
-
const { mkdir: mkdir2, writeFile: writeFile2, cp } = await import("node:fs/promises");
|
|
900
|
-
const { join: join2, dirname } = await import("node:path");
|
|
901
|
-
await mkdir2(outDir, { recursive: true });
|
|
902
|
-
const config = {
|
|
903
|
-
version: 3,
|
|
904
|
-
routes: [
|
|
905
|
-
// CDN-served static assets win before the render function runs.
|
|
906
|
-
{ handle: "filesystem" },
|
|
907
|
-
{ src: "/.*", dest: `/${functionName}` }
|
|
908
|
-
]
|
|
909
|
-
};
|
|
910
|
-
await writeFile2(join2(outDir, "config.json"), JSON.stringify(config, null, 2));
|
|
911
|
-
if (staticDir) {
|
|
912
|
-
await cp(staticDir, join2(outDir, "static"), { recursive: true });
|
|
913
|
-
}
|
|
914
|
-
let functionDir = null;
|
|
915
|
-
if (files && typeof files === "object") {
|
|
916
|
-
functionDir = join2(outDir, "functions", `${functionName}.func`);
|
|
917
|
-
await mkdir2(functionDir, { recursive: true });
|
|
918
|
-
const vcConfig = {
|
|
919
|
-
runtime,
|
|
920
|
-
handler,
|
|
921
|
-
launcherType: "Nodejs",
|
|
922
|
-
shouldAddHelpers: false,
|
|
923
|
-
supportsResponseStreaming: true
|
|
924
|
-
};
|
|
925
|
-
await writeFile2(join2(functionDir, ".vc-config.json"), JSON.stringify(vcConfig, null, 2));
|
|
926
|
-
for (const [rel, contents] of Object.entries(files)) {
|
|
927
|
-
const dest = join2(functionDir, rel);
|
|
928
|
-
await mkdir2(dirname(dest), { recursive: true });
|
|
929
|
-
await writeFile2(dest, contents);
|
|
930
|
-
}
|
|
931
|
-
if (!(handler in files)) {
|
|
932
|
-
console.warn(`[what-server] buildVercelOutput: files does not include the handler entry "${handler}" \u2014 the deploy will 500 until your build emits it.`);
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
return { config, outDir, functionDir };
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// packages/server/src/index.js
|
|
939
|
-
function createRenderContext(loaderData) {
|
|
940
|
-
return {
|
|
941
|
-
head: beginHeadCollection(),
|
|
942
|
-
loaderData,
|
|
943
|
-
resources: /* @__PURE__ */ new Map(),
|
|
944
|
-
resourceCounter: 0,
|
|
945
|
-
boundaryCounter: 0,
|
|
946
|
-
suspended: []
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
var _hydrationIdCounter = 0;
|
|
950
|
-
function resetHydrationId() {
|
|
951
|
-
_hydrationIdCounter = 0;
|
|
952
|
-
}
|
|
953
|
-
function nextHydrationId() {
|
|
954
|
-
return "h" + _hydrationIdCounter++;
|
|
955
|
-
}
|
|
956
|
-
function renderToHydratableString(vnode) {
|
|
957
|
-
resetHydrationId();
|
|
958
|
-
return _renderHydratable(vnode);
|
|
959
|
-
}
|
|
960
|
-
function _renderHydratable(vnode) {
|
|
961
|
-
if (vnode == null || vnode === false || vnode === true) return "";
|
|
962
|
-
if (typeof vnode === "string" || typeof vnode === "number") {
|
|
963
|
-
return escapeHtml(String(vnode));
|
|
964
|
-
}
|
|
965
|
-
if (typeof vnode === "function" && vnode._signal) {
|
|
966
|
-
return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;
|
|
967
|
-
}
|
|
968
|
-
if (typeof vnode === "function") {
|
|
969
|
-
try {
|
|
970
|
-
return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;
|
|
971
|
-
} catch (e) {
|
|
972
|
-
if (typeof process !== "undefined" && true) {
|
|
973
|
-
console.warn("[what-server] Error rendering reactive function in SSR:", e.message);
|
|
974
|
-
}
|
|
975
|
-
return "<!--$--><!--/$-->";
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
if (Array.isArray(vnode)) {
|
|
979
|
-
return `<!--[]-->${vnode.map(_renderHydratable).join("")}<!--/[]-->`;
|
|
980
|
-
}
|
|
981
|
-
if (typeof vnode.tag === "function") {
|
|
982
|
-
const hkId = nextHydrationId();
|
|
983
|
-
const result = vnode.tag({ ...vnode.props, children: vnode.children });
|
|
984
|
-
const html = _renderHydratable(result);
|
|
985
|
-
return injectHydrationKey(html, hkId);
|
|
986
|
-
}
|
|
987
|
-
const { tag, props, children } = vnode;
|
|
988
|
-
const attrs = renderAttrs(props || {});
|
|
989
|
-
const open = `<${tag}${attrs}>`;
|
|
990
|
-
if (VOID_ELEMENTS.has(tag)) return open;
|
|
991
|
-
const rawInner = _resolveInnerHTML(props);
|
|
992
|
-
const inner = rawInner != null ? String(rawInner) : children.map(_renderHydratable).join("");
|
|
993
|
-
return `${open}${inner}</${tag}>`;
|
|
994
|
-
}
|
|
995
|
-
function injectHydrationKey(html, hkId) {
|
|
996
|
-
const match = html.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);
|
|
997
|
-
if (match) {
|
|
998
|
-
const prefix = match[1];
|
|
999
|
-
const tagName = match[2];
|
|
1000
|
-
const insertAt = prefix.length + 1 + tagName.length;
|
|
1001
|
-
return html.slice(0, insertAt) + ` data-hk="${hkId}"` + html.slice(insertAt);
|
|
1002
|
-
}
|
|
1003
|
-
return html;
|
|
1004
|
-
}
|
|
1005
|
-
function renderToString(vnode) {
|
|
1006
|
-
if (vnode == null || vnode === false || vnode === true) return "";
|
|
1007
|
-
if (typeof vnode === "string" || typeof vnode === "number") {
|
|
1008
|
-
return escapeHtml(String(vnode));
|
|
1009
|
-
}
|
|
1010
|
-
if (typeof vnode === "function" && vnode._signal) {
|
|
1011
|
-
return renderToString(vnode());
|
|
1012
|
-
}
|
|
1013
|
-
if (typeof vnode === "function") {
|
|
1014
|
-
try {
|
|
1015
|
-
return renderToString(vnode());
|
|
1016
|
-
} catch (e) {
|
|
1017
|
-
if (typeof process !== "undefined" && true) {
|
|
1018
|
-
console.warn("[what-server] Error rendering reactive function in SSR:", e.message);
|
|
1019
|
-
}
|
|
1020
|
-
return "";
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
if (Array.isArray(vnode)) {
|
|
1024
|
-
return vnode.map(renderToString).join("");
|
|
1025
|
-
}
|
|
1026
|
-
if (vnode.tag === "__suspense") {
|
|
1027
|
-
try {
|
|
1028
|
-
return (vnode.children || []).map(renderToString).join("");
|
|
1029
|
-
} catch (e) {
|
|
1030
|
-
if (e && typeof e.then === "function") {
|
|
1031
|
-
return renderToString(vnode.props && vnode.props.fallback);
|
|
1032
|
-
}
|
|
1033
|
-
throw e;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
if (typeof vnode.tag === "function") {
|
|
1037
|
-
const result = vnode.tag({ ...vnode.props, children: vnode.children });
|
|
1038
|
-
return renderToString(result);
|
|
1039
|
-
}
|
|
1040
|
-
const { tag, props, children } = vnode;
|
|
1041
|
-
const attrs = renderAttrs(props || {});
|
|
1042
|
-
const open = `<${tag}${attrs}>`;
|
|
1043
|
-
if (VOID_ELEMENTS.has(tag)) return open;
|
|
1044
|
-
const rawInner = _resolveInnerHTML(props);
|
|
1045
|
-
const inner = rawInner != null ? String(rawInner) : children.map(renderToString).join("");
|
|
1046
|
-
return `${open}${inner}</${tag}>`;
|
|
1047
|
-
}
|
|
1048
|
-
function renderToStringWithHead(vnode) {
|
|
1049
|
-
const ctx = createRenderContext(void 0);
|
|
1050
|
-
const body = runWithServerContext(ctx, () => renderToString(vnode));
|
|
1051
|
-
return { body, head: endHeadCollection(ctx.head) };
|
|
1052
|
-
}
|
|
1053
|
-
async function renderPage(pageModule, reqCtx = {}) {
|
|
1054
|
-
const Component = pageModule.default || pageModule;
|
|
1055
|
-
const loaderData = typeof pageModule.loader === "function" ? await pageModule.loader(reqCtx) : void 0;
|
|
1056
|
-
const ctx = createRenderContext(loaderData);
|
|
1057
|
-
const params = reqCtx.params || {};
|
|
1058
|
-
const body = runWithServerContext(
|
|
1059
|
-
ctx,
|
|
1060
|
-
() => renderToString(h(Component, { ...params, loaderData }))
|
|
1061
|
-
);
|
|
1062
|
-
return { body, head: endHeadCollection(ctx.head), loaderData };
|
|
1063
|
-
}
|
|
1064
|
-
var MAX_RESOLVE_PASSES = 12;
|
|
1065
|
-
async function renderToStringAsync(vnode, ctx) {
|
|
1066
|
-
if (!ctx) ctx = createRenderContext(void 0);
|
|
1067
|
-
let body = "";
|
|
1068
|
-
for (let pass = 0; pass < MAX_RESOLVE_PASSES; pass++) {
|
|
1069
|
-
body = runWithServerContext(ctx, () => renderToString(vnode));
|
|
1070
|
-
const pending = [...ctx.resources.values()].filter((r) => r.status === "pending").map((r) => r.promise);
|
|
1071
|
-
if (pending.length === 0) break;
|
|
1072
|
-
await Promise.all(pending);
|
|
1073
|
-
}
|
|
1074
|
-
const resources = {};
|
|
1075
|
-
for (const [k, v] of ctx.resources) if (v.status === "ready") resources[k] = v.value;
|
|
1076
|
-
return { body, head: endHeadCollection(ctx.head), loaderData: ctx.loaderData, resources, ctx };
|
|
1077
|
-
}
|
|
1078
|
-
async function renderDocument(pageModule, reqCtx = {}, options = {}) {
|
|
1079
|
-
const Component = pageModule.default || pageModule;
|
|
1080
|
-
const loaderData = typeof pageModule.loader === "function" ? await pageModule.loader(reqCtx) : void 0;
|
|
1081
|
-
const ctx = createRenderContext(loaderData);
|
|
1082
|
-
const params = reqCtx.params || {};
|
|
1083
|
-
const { body, head, resources } = await renderToStringAsync(
|
|
1084
|
-
h(Component, { ...params, loaderData }),
|
|
1085
|
-
ctx
|
|
1086
|
-
);
|
|
1087
|
-
const payload = {
|
|
1088
|
-
loaderData: loaderData ?? null,
|
|
1089
|
-
resources,
|
|
1090
|
-
islandStores: getIslandStoresSnapshot()
|
|
1091
|
-
};
|
|
1092
|
-
return wrapHtmlDocument({ body, head, payload, options });
|
|
1093
|
-
}
|
|
1094
|
-
function wrapHtmlDocument({ body, head, payload, options = {} }) {
|
|
1095
|
-
const lang = options.lang || "en";
|
|
1096
|
-
const dataScript = `<script id="__what_data" type="application/json">${serializeState(payload)}<\/script>`;
|
|
1097
|
-
const clientScript = options.clientEntry ? `<script type="module" src="${escapeHtml(options.clientEntry)}"><\/script>` : "";
|
|
1098
|
-
const csrfHead = options.csrfToken ? csrfMetaTag(options.csrfToken) : "";
|
|
1099
|
-
const extraHead = csrfHead + (options.head || "");
|
|
1100
|
-
const bodyClass = options.bodyClass ? ` class="${escapeHtml(options.bodyClass)}"` : "";
|
|
1101
|
-
return `<!DOCTYPE html><html lang="${escapeHtml(lang)}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">${head || ""}${extraHead}</head><body${bodyClass}>${body}${dataScript}${clientScript}</body></html>`;
|
|
1102
|
-
}
|
|
1103
|
-
async function* renderToStream(vnode, ctx) {
|
|
1104
|
-
if (ctx === void 0) ctx = createRenderContext(void 0);
|
|
1105
|
-
if (vnode == null || vnode === false || vnode === true) return;
|
|
1106
|
-
if (typeof vnode === "string" || typeof vnode === "number") {
|
|
1107
|
-
yield escapeHtml(String(vnode));
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
if (typeof vnode === "function" && vnode._signal) {
|
|
1111
|
-
yield* renderToStream(vnode(), ctx);
|
|
1112
|
-
return;
|
|
1113
|
-
}
|
|
1114
|
-
if (typeof vnode === "function") {
|
|
1115
|
-
try {
|
|
1116
|
-
yield* renderToStream(vnode(), ctx);
|
|
1117
|
-
} catch (e) {
|
|
1118
|
-
if (typeof process !== "undefined" && true) {
|
|
1119
|
-
console.warn("[what-server] Error rendering reactive function in stream SSR:", e.message);
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
if (Array.isArray(vnode)) {
|
|
1125
|
-
for (const child of vnode) {
|
|
1126
|
-
yield* renderToStream(child, ctx);
|
|
1127
|
-
}
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
if (vnode.tag === "__suspense") {
|
|
1131
|
-
let html = null;
|
|
1132
|
-
for (let attempt = 0; attempt < MAX_RESOLVE_PASSES && html === null; attempt++) {
|
|
1133
|
-
let suspended = null;
|
|
1134
|
-
try {
|
|
1135
|
-
html = runWithServerContext(ctx, () => (vnode.children || []).map(renderToString).join(""));
|
|
1136
|
-
} catch (e) {
|
|
1137
|
-
if (e && typeof e.then === "function") suspended = e;
|
|
1138
|
-
else throw e;
|
|
1139
|
-
}
|
|
1140
|
-
if (html === null) {
|
|
1141
|
-
const pending = [...ctx.resources.values()].filter((r) => r.status === "pending").map((r) => r.promise);
|
|
1142
|
-
await Promise.all([suspended, ...pending].filter(Boolean));
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
if (html === null) {
|
|
1146
|
-
html = runWithServerContext(ctx, () => renderToString(vnode.props && vnode.props.fallback));
|
|
1147
|
-
}
|
|
1148
|
-
yield html;
|
|
1149
|
-
return;
|
|
1150
|
-
}
|
|
1151
|
-
if (typeof vnode.tag === "function") {
|
|
1152
|
-
try {
|
|
1153
|
-
const result = vnode.tag({ ...vnode.props, children: vnode.children });
|
|
1154
|
-
const resolved = result instanceof Promise ? await result : result;
|
|
1155
|
-
yield* renderToStream(resolved, ctx);
|
|
1156
|
-
} catch (e) {
|
|
1157
|
-
if (typeof process !== "undefined" && true) {
|
|
1158
|
-
console.warn("[what-server] Error rendering component in stream SSR:", e.message);
|
|
1159
|
-
}
|
|
1160
|
-
yield _isDevMode ? `<!-- SSR Error: ${escapeHtml(e.message || "Component error")} -->` : `<!-- SSR Error -->`;
|
|
1161
|
-
}
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
const { tag, props, children } = vnode;
|
|
1165
|
-
const attrs = renderAttrs(props || {});
|
|
1166
|
-
yield `<${tag}${attrs}>`;
|
|
1167
|
-
if (!VOID_ELEMENTS.has(tag)) {
|
|
1168
|
-
const rawInner = _resolveInnerHTML(props);
|
|
1169
|
-
if (rawInner != null) {
|
|
1170
|
-
yield String(rawInner);
|
|
1171
|
-
} else {
|
|
1172
|
-
for (const child of children) {
|
|
1173
|
-
yield* renderToStream(child, ctx);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
yield `</${tag}>`;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
function definePage(config) {
|
|
1180
|
-
return {
|
|
1181
|
-
// 'static' = pre-render at build time (default)
|
|
1182
|
-
// 'server' = render on each request
|
|
1183
|
-
// 'client' = render in browser (SPA)
|
|
1184
|
-
// 'hybrid' = static shell + islands
|
|
1185
|
-
mode: "static",
|
|
1186
|
-
...config
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
|
-
function generateStaticPage(page, data = {}) {
|
|
1190
|
-
const vnode = page.component(data);
|
|
1191
|
-
const html = renderToString(vnode);
|
|
1192
|
-
const islands = page.islands || [];
|
|
1193
|
-
return wrapDocument({
|
|
1194
|
-
title: page.title || "",
|
|
1195
|
-
meta: page.meta || {},
|
|
1196
|
-
body: html,
|
|
1197
|
-
islands,
|
|
1198
|
-
scripts: page.mode === "static" ? [] : page.scripts || [],
|
|
1199
|
-
styles: page.styles || [],
|
|
1200
|
-
mode: page.mode
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
function wrapDocument({ title, meta, body, islands, scripts, styles, mode }) {
|
|
1204
|
-
const metaTags = Object.entries(meta).map(([name, content]) => `<meta name="${escapeHtml(name)}" content="${escapeHtml(content)}">`).join("\n ");
|
|
1205
|
-
const styleTags = styles.map((href) => `<link rel="stylesheet" href="${escapeHtml(href)}">`).join("\n ");
|
|
1206
|
-
const islandScript = islands.length > 0 ? `
|
|
1207
|
-
<script type="module">
|
|
1208
|
-
import { hydrateIslands } from '/@what/islands.js';
|
|
1209
|
-
hydrateIslands();
|
|
1210
|
-
<\/script>` : "";
|
|
1211
|
-
const scriptTags = scripts.map((src) => `<script type="module" src="${escapeHtml(src)}"><\/script>`).join("\n ");
|
|
1212
|
-
const clientScript = mode === "client" ? `
|
|
1213
|
-
<script type="module" src="/@what/client.js"><\/script>` : "";
|
|
1214
|
-
return `<!DOCTYPE html>
|
|
1215
|
-
<html lang="en">
|
|
1216
|
-
<head>
|
|
1217
|
-
<meta charset="UTF-8">
|
|
1218
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1219
|
-
${metaTags}
|
|
1220
|
-
<title>${escapeHtml(title)}</title>
|
|
1221
|
-
${styleTags}
|
|
1222
|
-
</head>
|
|
1223
|
-
<body>
|
|
1224
|
-
<div id="app">${body}</div>
|
|
1225
|
-
${islandScript}
|
|
1226
|
-
${scriptTags}
|
|
1227
|
-
${clientScript}
|
|
1228
|
-
</body>
|
|
1229
|
-
</html>`;
|
|
1230
|
-
}
|
|
1231
|
-
function server(Component) {
|
|
1232
|
-
Component._server = true;
|
|
1233
|
-
return Component;
|
|
1234
|
-
}
|
|
1235
|
-
var _isDevMode = typeof process !== "undefined" ? true : true;
|
|
1236
|
-
function _resolveInnerHTML(props) {
|
|
1237
|
-
if (!props) return null;
|
|
1238
|
-
if (props.dangerouslySetInnerHTML) {
|
|
1239
|
-
return props.dangerouslySetInnerHTML.__html ?? null;
|
|
1240
|
-
}
|
|
1241
|
-
if (props.innerHTML && typeof props.innerHTML === "object" && "__html" in props.innerHTML) {
|
|
1242
|
-
return props.innerHTML.__html ?? null;
|
|
1243
|
-
}
|
|
1244
|
-
if (props.innerHTML != null && typeof props.innerHTML === "string") {
|
|
1245
|
-
if (_isDevMode) {
|
|
1246
|
-
console.warn(
|
|
1247
|
-
"[what-server] innerHTML received a raw string. This is a security risk (XSS). Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead."
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
return null;
|
|
1251
|
-
}
|
|
1252
|
-
return null;
|
|
1253
|
-
}
|
|
1254
|
-
var SAFE_ATTR_NAME = /^[a-zA-Z_:][a-zA-Z0-9:._-]*$/;
|
|
1255
|
-
function renderAttrs(props) {
|
|
1256
|
-
let out = "";
|
|
1257
|
-
for (const [key, val] of Object.entries(props)) {
|
|
1258
|
-
if (key === "key" || key === "ref" || key === "children" || key === "dangerouslySetInnerHTML" || key === "innerHTML") continue;
|
|
1259
|
-
if (key.startsWith("on") && key.length > 2) continue;
|
|
1260
|
-
if (val === false || val == null) continue;
|
|
1261
|
-
if (!SAFE_ATTR_NAME.test(key)) {
|
|
1262
|
-
if (_isDevMode) {
|
|
1263
|
-
console.warn(`[what-server] Skipping invalid attribute name in SSR: ${JSON.stringify(key)}`);
|
|
1264
|
-
}
|
|
1265
|
-
continue;
|
|
1266
|
-
}
|
|
1267
|
-
if (key === "className" || key === "class") {
|
|
1268
|
-
out += ` class="${escapeHtml(String(val))}"`;
|
|
1269
|
-
} else if (key === "style" && typeof val === "object") {
|
|
1270
|
-
const css = Object.entries(val).map(([p, v]) => `${camelToKebab(p)}:${v}`).join(";");
|
|
1271
|
-
out += ` style="${escapeHtml(css)}"`;
|
|
1272
|
-
} else if (val === true) {
|
|
1273
|
-
if (key.startsWith("aria-") || key === "role") {
|
|
1274
|
-
out += ` ${key}="true"`;
|
|
1275
|
-
} else {
|
|
1276
|
-
out += ` ${key}`;
|
|
1277
|
-
}
|
|
1278
|
-
} else {
|
|
1279
|
-
if (isUnsafeUrlAttribute(key, val)) continue;
|
|
1280
|
-
out += ` ${key}="${escapeHtml(String(val))}"`;
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
return out;
|
|
1284
|
-
}
|
|
1285
|
-
function isUnsafeUrlAttribute(key, val) {
|
|
1286
|
-
const normalizedKey = key.toLowerCase();
|
|
1287
|
-
if (!URL_ATTRS.has(normalizedKey)) return false;
|
|
1288
|
-
const normalizedValue = String(val).trim().replace(/[\u0000-\u001f\u007f\s]+/g, "").toLowerCase();
|
|
1289
|
-
return normalizedValue.startsWith("javascript:") || normalizedValue.startsWith("vbscript:") || normalizedValue.startsWith("data:");
|
|
1290
|
-
}
|
|
1291
|
-
var URL_ATTRS = /* @__PURE__ */ new Set([
|
|
1292
|
-
"href",
|
|
1293
|
-
"src",
|
|
1294
|
-
"action",
|
|
1295
|
-
"formaction",
|
|
1296
|
-
"xlink:href"
|
|
1297
|
-
]);
|
|
1298
|
-
function escapeHtml(str) {
|
|
1299
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1300
|
-
}
|
|
1301
|
-
function camelToKebab(str) {
|
|
1302
|
-
if (str.startsWith("--")) return str;
|
|
1303
|
-
return str.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
1304
|
-
}
|
|
1305
|
-
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
1306
|
-
"area",
|
|
1307
|
-
"base",
|
|
1308
|
-
"br",
|
|
1309
|
-
"col",
|
|
1310
|
-
"embed",
|
|
1311
|
-
"hr",
|
|
1312
|
-
"img",
|
|
1313
|
-
"input",
|
|
1314
|
-
"link",
|
|
1315
|
-
"meta",
|
|
1316
|
-
"param",
|
|
1317
|
-
"source",
|
|
1318
|
-
"track",
|
|
1319
|
-
"wbr"
|
|
1320
|
-
]);
|
|
1321
|
-
export {
|
|
1322
|
-
action,
|
|
1323
|
-
buildVercelOutput,
|
|
1324
|
-
createActionHandler,
|
|
1325
|
-
createCloudflareHandler,
|
|
1326
|
-
createRequestHandler,
|
|
1327
|
-
createServer,
|
|
1328
|
-
createVercelHandler,
|
|
1329
|
-
csrfMetaTag,
|
|
1330
|
-
definePage,
|
|
1331
|
-
exportStatic,
|
|
1332
|
-
fetchActionHandler,
|
|
1333
|
-
formAction,
|
|
1334
|
-
generateCsrfToken,
|
|
1335
|
-
generateStaticPage,
|
|
1336
|
-
getRegisteredActions,
|
|
1337
|
-
getRevalidationHandler,
|
|
1338
|
-
handleActionRequest,
|
|
1339
|
-
invalidatePath,
|
|
1340
|
-
nodeActionMiddleware,
|
|
1341
|
-
onRevalidate,
|
|
1342
|
-
renderDocument,
|
|
1343
|
-
renderPage,
|
|
1344
|
-
renderToHydratableString,
|
|
1345
|
-
renderToStream,
|
|
1346
|
-
renderToString,
|
|
1347
|
-
renderToStringAsync,
|
|
1348
|
-
renderToStringWithHead,
|
|
1349
|
-
revalidatePath,
|
|
1350
|
-
revalidateTag,
|
|
1351
|
-
serializeState,
|
|
1352
|
-
server,
|
|
1353
|
-
setRevalidationHandler,
|
|
1354
|
-
toNodeListener,
|
|
1355
|
-
useAction,
|
|
1356
|
-
useFormAction,
|
|
1357
|
-
useMutation,
|
|
1358
|
-
useOptimistic,
|
|
1359
|
-
validateCsrfToken,
|
|
1360
|
-
whatMiddleware
|
|
1361
|
-
};
|
|
1362
|
-
//# sourceMappingURL=index.js.map
|