@solidjs/web 2.0.0-beta.26 → 2.0.0-beta.28
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/dev.cjs +3 -1
- package/dist/dev.js +3 -2
- package/dist/server.cjs +64 -84
- package/dist/server.js +65 -86
- package/dist/web.cjs +3 -1
- package/dist/web.js +3 -2
- package/frames/dist/client.cjs +69 -21
- package/frames/dist/client.dev.cjs +69 -21
- package/frames/dist/client.dev.js +70 -22
- package/frames/dist/client.js +70 -22
- package/frames/dist/server.cjs +55 -50
- package/frames/dist/server.js +55 -50
- package/package.json +4 -4
- package/serialization/dist/serialization.cjs +6 -3
- package/serialization/dist/serialization.js +6 -3
- package/serialization/types/index.d.ts +7 -1
- package/serialization/types-cjs/index.d.cts +7 -1
- package/server-functions/dist/client.cjs +28 -2
- package/server-functions/dist/client.js +25 -3
- package/server-functions/dist/server.cjs +194 -5
- package/server-functions/dist/server.js +187 -6
- package/types/client.d.ts +3 -3
- package/types/frames/client.d.ts +1 -0
- package/types/frames/serializer.d.ts +7 -1
- package/types/frames/server.d.ts +28 -0
- package/types/jsx.d.ts +1 -1
- package/types/response.d.ts +10 -0
- package/types/serializer.d.ts +7 -1
- package/types/server-functions/client.d.ts +4 -0
- package/types/server-functions/flash.d.ts +38 -0
- package/types/server-functions/server.d.ts +109 -6
- package/types/server-functions/shared.d.ts +48 -0
- package/types-cjs/client.d.cts +3 -3
- package/types-cjs/frames/client.d.cts +1 -0
- package/types-cjs/frames/serializer.d.cts +7 -1
- package/types-cjs/frames/server.d.cts +28 -0
- package/types-cjs/jsx.d.cts +1 -1
- package/types-cjs/response.d.cts +10 -0
- package/types-cjs/serializer.d.cts +7 -1
- package/types-cjs/server-functions/client.d.cts +4 -0
- package/types-cjs/server-functions/flash.d.cts +38 -0
- package/types-cjs/server-functions/server.d.cts +109 -6
- package/types-cjs/server-functions/shared.d.cts +48 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { fromCrossJSON, Feature } from 'seroval';
|
|
2
2
|
import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
|
|
3
3
|
|
|
4
|
+
const REVALIDATE_HEADER = "X-Revalidate";
|
|
5
|
+
|
|
4
6
|
Feature.AggregateError | Feature.BigIntTypedArray;
|
|
5
7
|
const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
|
|
6
8
|
CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
|
|
@@ -96,6 +98,14 @@ const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
|
96
98
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
97
99
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
98
100
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
101
|
+
const FLASH_COOKIE = "flash";
|
|
102
|
+
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
103
|
+
function hasFlashCookie(cookieHeader) {
|
|
104
|
+
return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
|
|
105
|
+
}
|
|
106
|
+
function clearFlashCookie() {
|
|
107
|
+
return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
|
|
108
|
+
}
|
|
99
109
|
const BodyFormat = {
|
|
100
110
|
Serialized: "0",
|
|
101
111
|
String: "1",
|
|
@@ -275,6 +285,18 @@ async function decodeResponse(response, codecOptions) {
|
|
|
275
285
|
if (!response.body) return undefined;
|
|
276
286
|
return await extractBody(response, codecOptions === undefined ? codecConfig.codec : codecOptions);
|
|
277
287
|
}
|
|
288
|
+
async function decodeResponsePayload(response, codecOptions) {
|
|
289
|
+
const decoded = await decodeResponse(response, codecOptions);
|
|
290
|
+
if (decoded !== undefined && response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
291
|
+
return {
|
|
292
|
+
value: decoded.value,
|
|
293
|
+
flightData: decoded.data
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
value: decoded
|
|
298
|
+
};
|
|
299
|
+
}
|
|
278
300
|
|
|
279
301
|
const config = {
|
|
280
302
|
endpoint: "/_server",
|
|
@@ -401,13 +423,13 @@ async function fetchServerFunction(base, id, options, args, meta) {
|
|
|
401
423
|
await consumer(payload.data, {
|
|
402
424
|
response
|
|
403
425
|
});
|
|
404
|
-
if (response.headers.has(ERROR_HEADER) && !response.headers.has("Location") && !response.headers.has(
|
|
426
|
+
if (response.headers.has(ERROR_HEADER) && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
|
|
405
427
|
throw payload.value;
|
|
406
428
|
}
|
|
407
429
|
return payload.value;
|
|
408
430
|
}
|
|
409
431
|
}
|
|
410
|
-
if (response.headers.has("Location") || response.headers.has(
|
|
432
|
+
if (response.headers.has("Location") || response.headers.has(REVALIDATE_HEADER) || response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
411
433
|
return response;
|
|
412
434
|
}
|
|
413
435
|
const result = await decodeResponse(response.clone());
|
|
@@ -484,4 +506,4 @@ function registerServerReference() {
|
|
|
484
506
|
throw new Error("registerServerReference must not be called in the client build");
|
|
485
507
|
}
|
|
486
508
|
|
|
487
|
-
export { ERROR_HEADER, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, configureServerFunctionsClient, createServerReference, decodeErrorHeaderValue, decodeResponse, encodeErrorHeaderValue, getServerFunctionMetadata, isServerFunction, registerServerReference, subscribeFlightData, withMeta };
|
|
509
|
+
export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsClient, createServerReference, decodeErrorHeaderValue, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, getServerFunctionMetadata, hasFlashCookie, isServerFunction, registerServerReference, subscribeFlightData, withMeta };
|
|
@@ -8,8 +8,10 @@ const ENVELOPE = Symbol.for("solid.ResponseEnvelope");
|
|
|
8
8
|
function isResponseEnvelope(value) {
|
|
9
9
|
return !!(value && typeof value === "object" && value[ENVELOPE]);
|
|
10
10
|
}
|
|
11
|
+
const REVALIDATE_HEADER = "X-Revalidate";
|
|
11
12
|
|
|
12
13
|
seroval.Feature.AggregateError | seroval.Feature.BigIntTypedArray;
|
|
14
|
+
const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : seroval.Feature.ErrorPrototypeStack;
|
|
13
15
|
const DEFAULT_WEB_PLUGINS = Object.freeze([web.AbortSignalPlugin,
|
|
14
16
|
web.CustomEventPlugin, web.DOMExceptionPlugin, web.EventPlugin,
|
|
15
17
|
web.FormDataPlugin, web.HeadersPlugin, web.ReadableStreamPlugin, web.RequestPlugin, web.ResponsePlugin, web.URLSearchParamsPlugin, web.URLPlugin]);
|
|
@@ -35,11 +37,13 @@ function serializeJSON(value, {
|
|
|
35
37
|
onError,
|
|
36
38
|
...codecOptions
|
|
37
39
|
}) {
|
|
40
|
+
const resolved = resolveCodecOptions(codecOptions);
|
|
38
41
|
return seroval.toCrossJSONStream(value, {
|
|
39
42
|
onParse,
|
|
40
43
|
onDone,
|
|
41
44
|
onError,
|
|
42
|
-
...
|
|
45
|
+
...resolved,
|
|
46
|
+
disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures()
|
|
43
47
|
});
|
|
44
48
|
}
|
|
45
49
|
function createJSONDeserializer(options) {
|
|
@@ -117,6 +121,18 @@ const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
|
117
121
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
118
122
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
119
123
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
124
|
+
const FLASH_COOKIE = "flash";
|
|
125
|
+
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
126
|
+
function hasFlashCookie(cookieHeader) {
|
|
127
|
+
return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
|
|
128
|
+
}
|
|
129
|
+
function matchFlashCookie(cookieHeader) {
|
|
130
|
+
const match = cookieHeader && cookieHeader.match(FLASH_MATCHER);
|
|
131
|
+
return match ? match[1] : undefined;
|
|
132
|
+
}
|
|
133
|
+
function clearFlashCookie() {
|
|
134
|
+
return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
|
|
135
|
+
}
|
|
120
136
|
const BodyFormat = {
|
|
121
137
|
Serialized: "0",
|
|
122
138
|
String: "1",
|
|
@@ -328,12 +344,74 @@ async function decodeResponse(response, codecOptions) {
|
|
|
328
344
|
if (!response.body) return undefined;
|
|
329
345
|
return await extractBody(response, codecOptions === undefined ? codecConfig.codec : codecOptions);
|
|
330
346
|
}
|
|
347
|
+
async function decodeResponsePayload(response, codecOptions) {
|
|
348
|
+
const decoded = await decodeResponse(response, codecOptions);
|
|
349
|
+
if (decoded !== undefined && response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
350
|
+
return {
|
|
351
|
+
value: decoded.value,
|
|
352
|
+
flightData: decoded.data
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
value: decoded
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function encodeInputValue(value) {
|
|
361
|
+
if (value instanceof FormData) return {
|
|
362
|
+
$f: [...value.entries()].filter(([, v]) => typeof v === "string")
|
|
363
|
+
};
|
|
364
|
+
if (value instanceof URLSearchParams) return {
|
|
365
|
+
$u: [...value.entries()]
|
|
366
|
+
};
|
|
367
|
+
return value;
|
|
368
|
+
}
|
|
369
|
+
function decodeInputValue(value) {
|
|
370
|
+
if (value && typeof value === "object") {
|
|
371
|
+
if (Array.isArray(value.$f)) {
|
|
372
|
+
const form = new FormData();
|
|
373
|
+
for (const [k, v] of value.$f) form.append(k, v);
|
|
374
|
+
return form;
|
|
375
|
+
}
|
|
376
|
+
if (Array.isArray(value.$u)) return new URLSearchParams(value.$u);
|
|
377
|
+
}
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
function encodeFlashCookie(url, result, input, thrown) {
|
|
381
|
+
const isError = result instanceof Error;
|
|
382
|
+
const payload = {
|
|
383
|
+
url,
|
|
384
|
+
result: isError ? result.message : result,
|
|
385
|
+
error: isError,
|
|
386
|
+
thrown: !!thrown,
|
|
387
|
+
input: input.map(encodeInputValue)
|
|
388
|
+
};
|
|
389
|
+
return `${FLASH_COOKIE}=${encodeURIComponent(JSON.stringify(payload))}; Secure; HttpOnly; Path=/`;
|
|
390
|
+
}
|
|
391
|
+
function decodeFlashCookie(cookieHeader) {
|
|
392
|
+
const match = matchFlashCookie(cookieHeader);
|
|
393
|
+
if (!match) return;
|
|
394
|
+
try {
|
|
395
|
+
const payload = JSON.parse(decodeURIComponent(match));
|
|
396
|
+
if (!payload || !payload.result) return;
|
|
397
|
+
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
398
|
+
return {
|
|
399
|
+
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
400
|
+
url: payload.url,
|
|
401
|
+
result: payload.thrown ? undefined : result,
|
|
402
|
+
error: payload.thrown ? result : undefined
|
|
403
|
+
};
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error(error);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
331
408
|
|
|
332
409
|
const config = {
|
|
333
410
|
provideEvent: undefined,
|
|
334
411
|
collectFlightData: undefined,
|
|
335
412
|
transformResult: undefined,
|
|
336
413
|
transformDirectResult: undefined,
|
|
414
|
+
handleNoJS: undefined,
|
|
337
415
|
endpoint: "/_server"
|
|
338
416
|
};
|
|
339
417
|
function configureServerFunctionsServer({
|
|
@@ -341,6 +419,7 @@ function configureServerFunctionsServer({
|
|
|
341
419
|
collectFlightData,
|
|
342
420
|
transformResult,
|
|
343
421
|
transformDirectResult,
|
|
422
|
+
handleNoJS,
|
|
344
423
|
endpoint,
|
|
345
424
|
codec
|
|
346
425
|
} = {}) {
|
|
@@ -348,6 +427,7 @@ function configureServerFunctionsServer({
|
|
|
348
427
|
if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
|
|
349
428
|
if (transformResult !== undefined) config.transformResult = transformResult;
|
|
350
429
|
if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
|
|
430
|
+
if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
|
|
351
431
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
352
432
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
353
433
|
}
|
|
@@ -465,6 +545,8 @@ async function parseArguments(request, url, instance, codec) {
|
|
|
465
545
|
return parsed;
|
|
466
546
|
}
|
|
467
547
|
async function foldFlightData(hook, event, headers, outcome) {
|
|
548
|
+
if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
|
|
549
|
+
digestOutcome(event, outcome);
|
|
468
550
|
const data = await hook(event, outcome);
|
|
469
551
|
if (data === undefined) return outcome.value;
|
|
470
552
|
headers.set(SINGLE_FLIGHT_HEADER, "true");
|
|
@@ -473,6 +555,104 @@ async function foldFlightData(hook, event, headers, outcome) {
|
|
|
473
555
|
data
|
|
474
556
|
};
|
|
475
557
|
}
|
|
558
|
+
function digestOutcome(event, outcome) {
|
|
559
|
+
const {
|
|
560
|
+
request,
|
|
561
|
+
response
|
|
562
|
+
} = outcome;
|
|
563
|
+
outcome.revalidateKeys = response?.headers.get(REVALIDATE_HEADER)?.split(",");
|
|
564
|
+
outcome.foldedHeaders = foldSetCookies(request.headers, [...(event.response?.headers?.getSetCookie() ?? []), ...(response?.headers?.getSetCookie() ?? [])]);
|
|
565
|
+
try {
|
|
566
|
+
const referrer = request.headers.get("referer");
|
|
567
|
+
if (referrer) {
|
|
568
|
+
const location = response?.headers.get("Location");
|
|
569
|
+
const target = location ? new URL(location, request.url) : new URL(referrer);
|
|
570
|
+
if (target.origin === new URL(request.url).origin) outcome.targetUrl = target.toString();
|
|
571
|
+
}
|
|
572
|
+
} catch {
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function parseSetCookie(setCookie) {
|
|
576
|
+
const [pair, ...attributes] = setCookie.split(";");
|
|
577
|
+
const eq = pair.indexOf("=");
|
|
578
|
+
if (eq < 0) return undefined;
|
|
579
|
+
const parsed = {
|
|
580
|
+
name: pair.slice(0, eq).trim(),
|
|
581
|
+
value: pair.slice(eq + 1).trim()
|
|
582
|
+
};
|
|
583
|
+
for (const attribute of attributes) {
|
|
584
|
+
const attrEq = attribute.indexOf("=");
|
|
585
|
+
const key = (attrEq < 0 ? attribute : attribute.slice(0, attrEq)).trim().toLowerCase();
|
|
586
|
+
const value = attrEq < 0 ? "" : attribute.slice(attrEq + 1).trim();
|
|
587
|
+
if (key === "max-age") parsed.maxAge = Number(value);else if (key === "expires") parsed.expires = new Date(value);
|
|
588
|
+
}
|
|
589
|
+
return parsed;
|
|
590
|
+
}
|
|
591
|
+
function foldSetCookies(headers, setCookies) {
|
|
592
|
+
const folded = new Headers(headers);
|
|
593
|
+
if (!setCookies.length) return folded;
|
|
594
|
+
const cookies = {};
|
|
595
|
+
for (const pair of folded.get("cookie")?.split(";") ?? []) {
|
|
596
|
+
const eq = pair.indexOf("=");
|
|
597
|
+
if (eq > -1) cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
598
|
+
}
|
|
599
|
+
for (const setCookie of setCookies) {
|
|
600
|
+
const parsed = parseSetCookie(setCookie);
|
|
601
|
+
if (!parsed) continue;
|
|
602
|
+
if (parsed.maxAge != null && parsed.maxAge <= 0 || parsed.expires != null && parsed.expires.getTime() <= Date.now()) {
|
|
603
|
+
delete cookies[parsed.name];
|
|
604
|
+
} else {
|
|
605
|
+
cookies[parsed.name] = parsed.value;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
folded.delete("cookie");
|
|
609
|
+
const serialized = Object.entries(cookies).map(([name, value]) => `${name}=${value}`).join("; ");
|
|
610
|
+
if (serialized) folded.set("cookie", serialized);
|
|
611
|
+
return folded;
|
|
612
|
+
}
|
|
613
|
+
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
614
|
+
function createNoJSHandler({
|
|
615
|
+
base = ""
|
|
616
|
+
} = {}) {
|
|
617
|
+
return function handleNoJS(result, request, args, thrown) {
|
|
618
|
+
const url = new URL(request.url);
|
|
619
|
+
let back = new URL(base || "/", url.origin).toString();
|
|
620
|
+
try {
|
|
621
|
+
const referer = request.headers.get("referer");
|
|
622
|
+
if (referer) back = new URL(referer).toString();
|
|
623
|
+
} catch {}
|
|
624
|
+
let status = 303;
|
|
625
|
+
let headers;
|
|
626
|
+
if (result instanceof Response) {
|
|
627
|
+
headers = new Headers(result.headers);
|
|
628
|
+
if (result.headers.has("Location")) {
|
|
629
|
+
headers.set("Location", new URL(result.headers.get("Location"), url.origin + base).toString());
|
|
630
|
+
if (validRedirectStatuses.has(result.status)) status = result.status;
|
|
631
|
+
} else {
|
|
632
|
+
headers.set("Location", back);
|
|
633
|
+
}
|
|
634
|
+
headers.delete("Content-Type");
|
|
635
|
+
headers.delete("Content-Length");
|
|
636
|
+
} else {
|
|
637
|
+
headers = new Headers({
|
|
638
|
+
Location: back
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
if (result && !(result instanceof Response)) {
|
|
642
|
+
headers.append("Set-Cookie", encodeFlashCookie(url.pathname + url.search, result, args, thrown));
|
|
643
|
+
}
|
|
644
|
+
return new Response(null, {
|
|
645
|
+
status,
|
|
646
|
+
headers
|
|
647
|
+
});
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
let defaultNoJSHandler;
|
|
651
|
+
function isFormPost(request) {
|
|
652
|
+
if (request.method !== "POST" || request.headers.has(BODY_FORMAT_HEADER)) return false;
|
|
653
|
+
const type = request.headers.get("content-type") || "";
|
|
654
|
+
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
655
|
+
}
|
|
476
656
|
function serializedResponse(value, headers, codec) {
|
|
477
657
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
|
|
478
658
|
headers.set("Content-Type", "text/plain");
|
|
@@ -530,6 +710,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
530
710
|
const provide = options.provideEvent || provideEvent;
|
|
531
711
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
532
712
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
713
|
+
const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
|
|
533
714
|
const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
|
|
534
715
|
const parsed = await parseArguments(request, url, instance, codec);
|
|
535
716
|
const headers = new Headers();
|
|
@@ -553,7 +734,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
553
734
|
response,
|
|
554
735
|
value
|
|
555
736
|
} = result;
|
|
556
|
-
if (!instance && !
|
|
737
|
+
if (!instance && !handleNoJS && response && response.body) {
|
|
557
738
|
return response;
|
|
558
739
|
}
|
|
559
740
|
if (response && response.headers) {
|
|
@@ -589,7 +770,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
589
770
|
});
|
|
590
771
|
}
|
|
591
772
|
if (!instance) {
|
|
592
|
-
if (
|
|
773
|
+
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
593
774
|
if (result instanceof Response) return result;
|
|
594
775
|
return encodeResult(result, headers, 200, codec);
|
|
595
776
|
}
|
|
@@ -641,13 +822,13 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
641
822
|
}
|
|
642
823
|
headers.set(ERROR_HEADER, "true");
|
|
643
824
|
if (!instance) {
|
|
644
|
-
if (
|
|
825
|
+
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
645
826
|
if (x instanceof Response) return x;
|
|
646
827
|
}
|
|
647
828
|
return encodeResult(x, headers, status, codec);
|
|
648
829
|
}
|
|
649
830
|
if (!instance) {
|
|
650
|
-
if (
|
|
831
|
+
if (handleNoJS) return handleNoJS(x, request, parsed, true);
|
|
651
832
|
const message = x instanceof Error ? x.message : String(x);
|
|
652
833
|
return new Response(process.env.NODE_ENV === "development" ? message : null, {
|
|
653
834
|
status: 500
|
|
@@ -660,19 +841,27 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
660
841
|
}
|
|
661
842
|
|
|
662
843
|
exports.ERROR_HEADER = ERROR_HEADER;
|
|
844
|
+
exports.FLASH_COOKIE = FLASH_COOKIE;
|
|
663
845
|
exports.FUNCTION_HEADER = FUNCTION_HEADER;
|
|
664
846
|
exports.GET = GET;
|
|
665
847
|
exports.INSTANCE_HEADER = INSTANCE_HEADER;
|
|
666
848
|
exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
|
|
849
|
+
exports.clearFlashCookie = clearFlashCookie;
|
|
667
850
|
exports.configureServerFunctionsServer = configureServerFunctionsServer;
|
|
851
|
+
exports.createNoJSHandler = createNoJSHandler;
|
|
668
852
|
exports.createServerReference = createServerReference;
|
|
669
853
|
exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
|
|
854
|
+
exports.decodeFlashCookie = decodeFlashCookie;
|
|
670
855
|
exports.decodeResponse = decodeResponse;
|
|
856
|
+
exports.decodeResponsePayload = decodeResponsePayload;
|
|
671
857
|
exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
|
|
858
|
+
exports.encodeFlashCookie = encodeFlashCookie;
|
|
859
|
+
exports.foldSetCookies = foldSetCookies;
|
|
672
860
|
exports.getServerFunction = getServerFunction;
|
|
673
861
|
exports.getServerFunctionMeta = getServerFunctionMeta;
|
|
674
862
|
exports.getServerFunctionMetadata = getServerFunctionMetadata;
|
|
675
863
|
exports.handleServerFunctionRequest = handleServerFunctionRequest;
|
|
864
|
+
exports.hasFlashCookie = hasFlashCookie;
|
|
676
865
|
exports.isServerFunction = isServerFunction;
|
|
677
866
|
exports.registerServerFunction = registerServerFunction;
|
|
678
867
|
exports.registerServerReference = registerServerReference;
|
|
@@ -6,8 +6,10 @@ const ENVELOPE = Symbol.for("solid.ResponseEnvelope");
|
|
|
6
6
|
function isResponseEnvelope(value) {
|
|
7
7
|
return !!(value && typeof value === "object" && value[ENVELOPE]);
|
|
8
8
|
}
|
|
9
|
+
const REVALIDATE_HEADER = "X-Revalidate";
|
|
9
10
|
|
|
10
11
|
Feature.AggregateError | Feature.BigIntTypedArray;
|
|
12
|
+
const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : Feature.ErrorPrototypeStack;
|
|
11
13
|
const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
|
|
12
14
|
CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
|
|
13
15
|
FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin]);
|
|
@@ -33,11 +35,13 @@ function serializeJSON(value, {
|
|
|
33
35
|
onError,
|
|
34
36
|
...codecOptions
|
|
35
37
|
}) {
|
|
38
|
+
const resolved = resolveCodecOptions(codecOptions);
|
|
36
39
|
return toCrossJSONStream(value, {
|
|
37
40
|
onParse,
|
|
38
41
|
onDone,
|
|
39
42
|
onError,
|
|
40
|
-
...
|
|
43
|
+
...resolved,
|
|
44
|
+
disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures()
|
|
41
45
|
});
|
|
42
46
|
}
|
|
43
47
|
function createJSONDeserializer(options) {
|
|
@@ -115,6 +119,18 @@ const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
|
115
119
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
116
120
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
117
121
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
122
|
+
const FLASH_COOKIE = "flash";
|
|
123
|
+
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
124
|
+
function hasFlashCookie(cookieHeader) {
|
|
125
|
+
return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
|
|
126
|
+
}
|
|
127
|
+
function matchFlashCookie(cookieHeader) {
|
|
128
|
+
const match = cookieHeader && cookieHeader.match(FLASH_MATCHER);
|
|
129
|
+
return match ? match[1] : undefined;
|
|
130
|
+
}
|
|
131
|
+
function clearFlashCookie() {
|
|
132
|
+
return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
|
|
133
|
+
}
|
|
118
134
|
const BodyFormat = {
|
|
119
135
|
Serialized: "0",
|
|
120
136
|
String: "1",
|
|
@@ -326,12 +342,74 @@ async function decodeResponse(response, codecOptions) {
|
|
|
326
342
|
if (!response.body) return undefined;
|
|
327
343
|
return await extractBody(response, codecOptions === undefined ? codecConfig.codec : codecOptions);
|
|
328
344
|
}
|
|
345
|
+
async function decodeResponsePayload(response, codecOptions) {
|
|
346
|
+
const decoded = await decodeResponse(response, codecOptions);
|
|
347
|
+
if (decoded !== undefined && response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
348
|
+
return {
|
|
349
|
+
value: decoded.value,
|
|
350
|
+
flightData: decoded.data
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
value: decoded
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function encodeInputValue(value) {
|
|
359
|
+
if (value instanceof FormData) return {
|
|
360
|
+
$f: [...value.entries()].filter(([, v]) => typeof v === "string")
|
|
361
|
+
};
|
|
362
|
+
if (value instanceof URLSearchParams) return {
|
|
363
|
+
$u: [...value.entries()]
|
|
364
|
+
};
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
function decodeInputValue(value) {
|
|
368
|
+
if (value && typeof value === "object") {
|
|
369
|
+
if (Array.isArray(value.$f)) {
|
|
370
|
+
const form = new FormData();
|
|
371
|
+
for (const [k, v] of value.$f) form.append(k, v);
|
|
372
|
+
return form;
|
|
373
|
+
}
|
|
374
|
+
if (Array.isArray(value.$u)) return new URLSearchParams(value.$u);
|
|
375
|
+
}
|
|
376
|
+
return value;
|
|
377
|
+
}
|
|
378
|
+
function encodeFlashCookie(url, result, input, thrown) {
|
|
379
|
+
const isError = result instanceof Error;
|
|
380
|
+
const payload = {
|
|
381
|
+
url,
|
|
382
|
+
result: isError ? result.message : result,
|
|
383
|
+
error: isError,
|
|
384
|
+
thrown: !!thrown,
|
|
385
|
+
input: input.map(encodeInputValue)
|
|
386
|
+
};
|
|
387
|
+
return `${FLASH_COOKIE}=${encodeURIComponent(JSON.stringify(payload))}; Secure; HttpOnly; Path=/`;
|
|
388
|
+
}
|
|
389
|
+
function decodeFlashCookie(cookieHeader) {
|
|
390
|
+
const match = matchFlashCookie(cookieHeader);
|
|
391
|
+
if (!match) return;
|
|
392
|
+
try {
|
|
393
|
+
const payload = JSON.parse(decodeURIComponent(match));
|
|
394
|
+
if (!payload || !payload.result) return;
|
|
395
|
+
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
396
|
+
return {
|
|
397
|
+
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
398
|
+
url: payload.url,
|
|
399
|
+
result: payload.thrown ? undefined : result,
|
|
400
|
+
error: payload.thrown ? result : undefined
|
|
401
|
+
};
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error(error);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
329
406
|
|
|
330
407
|
const config = {
|
|
331
408
|
provideEvent: undefined,
|
|
332
409
|
collectFlightData: undefined,
|
|
333
410
|
transformResult: undefined,
|
|
334
411
|
transformDirectResult: undefined,
|
|
412
|
+
handleNoJS: undefined,
|
|
335
413
|
endpoint: "/_server"
|
|
336
414
|
};
|
|
337
415
|
function configureServerFunctionsServer({
|
|
@@ -339,6 +417,7 @@ function configureServerFunctionsServer({
|
|
|
339
417
|
collectFlightData,
|
|
340
418
|
transformResult,
|
|
341
419
|
transformDirectResult,
|
|
420
|
+
handleNoJS,
|
|
342
421
|
endpoint,
|
|
343
422
|
codec
|
|
344
423
|
} = {}) {
|
|
@@ -346,6 +425,7 @@ function configureServerFunctionsServer({
|
|
|
346
425
|
if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
|
|
347
426
|
if (transformResult !== undefined) config.transformResult = transformResult;
|
|
348
427
|
if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
|
|
428
|
+
if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
|
|
349
429
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
350
430
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
351
431
|
}
|
|
@@ -463,6 +543,8 @@ async function parseArguments(request, url, instance, codec) {
|
|
|
463
543
|
return parsed;
|
|
464
544
|
}
|
|
465
545
|
async function foldFlightData(hook, event, headers, outcome) {
|
|
546
|
+
if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
|
|
547
|
+
digestOutcome(event, outcome);
|
|
466
548
|
const data = await hook(event, outcome);
|
|
467
549
|
if (data === undefined) return outcome.value;
|
|
468
550
|
headers.set(SINGLE_FLIGHT_HEADER, "true");
|
|
@@ -471,6 +553,104 @@ async function foldFlightData(hook, event, headers, outcome) {
|
|
|
471
553
|
data
|
|
472
554
|
};
|
|
473
555
|
}
|
|
556
|
+
function digestOutcome(event, outcome) {
|
|
557
|
+
const {
|
|
558
|
+
request,
|
|
559
|
+
response
|
|
560
|
+
} = outcome;
|
|
561
|
+
outcome.revalidateKeys = response?.headers.get(REVALIDATE_HEADER)?.split(",");
|
|
562
|
+
outcome.foldedHeaders = foldSetCookies(request.headers, [...(event.response?.headers?.getSetCookie() ?? []), ...(response?.headers?.getSetCookie() ?? [])]);
|
|
563
|
+
try {
|
|
564
|
+
const referrer = request.headers.get("referer");
|
|
565
|
+
if (referrer) {
|
|
566
|
+
const location = response?.headers.get("Location");
|
|
567
|
+
const target = location ? new URL(location, request.url) : new URL(referrer);
|
|
568
|
+
if (target.origin === new URL(request.url).origin) outcome.targetUrl = target.toString();
|
|
569
|
+
}
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function parseSetCookie(setCookie) {
|
|
574
|
+
const [pair, ...attributes] = setCookie.split(";");
|
|
575
|
+
const eq = pair.indexOf("=");
|
|
576
|
+
if (eq < 0) return undefined;
|
|
577
|
+
const parsed = {
|
|
578
|
+
name: pair.slice(0, eq).trim(),
|
|
579
|
+
value: pair.slice(eq + 1).trim()
|
|
580
|
+
};
|
|
581
|
+
for (const attribute of attributes) {
|
|
582
|
+
const attrEq = attribute.indexOf("=");
|
|
583
|
+
const key = (attrEq < 0 ? attribute : attribute.slice(0, attrEq)).trim().toLowerCase();
|
|
584
|
+
const value = attrEq < 0 ? "" : attribute.slice(attrEq + 1).trim();
|
|
585
|
+
if (key === "max-age") parsed.maxAge = Number(value);else if (key === "expires") parsed.expires = new Date(value);
|
|
586
|
+
}
|
|
587
|
+
return parsed;
|
|
588
|
+
}
|
|
589
|
+
function foldSetCookies(headers, setCookies) {
|
|
590
|
+
const folded = new Headers(headers);
|
|
591
|
+
if (!setCookies.length) return folded;
|
|
592
|
+
const cookies = {};
|
|
593
|
+
for (const pair of folded.get("cookie")?.split(";") ?? []) {
|
|
594
|
+
const eq = pair.indexOf("=");
|
|
595
|
+
if (eq > -1) cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
596
|
+
}
|
|
597
|
+
for (const setCookie of setCookies) {
|
|
598
|
+
const parsed = parseSetCookie(setCookie);
|
|
599
|
+
if (!parsed) continue;
|
|
600
|
+
if (parsed.maxAge != null && parsed.maxAge <= 0 || parsed.expires != null && parsed.expires.getTime() <= Date.now()) {
|
|
601
|
+
delete cookies[parsed.name];
|
|
602
|
+
} else {
|
|
603
|
+
cookies[parsed.name] = parsed.value;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
folded.delete("cookie");
|
|
607
|
+
const serialized = Object.entries(cookies).map(([name, value]) => `${name}=${value}`).join("; ");
|
|
608
|
+
if (serialized) folded.set("cookie", serialized);
|
|
609
|
+
return folded;
|
|
610
|
+
}
|
|
611
|
+
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
612
|
+
function createNoJSHandler({
|
|
613
|
+
base = ""
|
|
614
|
+
} = {}) {
|
|
615
|
+
return function handleNoJS(result, request, args, thrown) {
|
|
616
|
+
const url = new URL(request.url);
|
|
617
|
+
let back = new URL(base || "/", url.origin).toString();
|
|
618
|
+
try {
|
|
619
|
+
const referer = request.headers.get("referer");
|
|
620
|
+
if (referer) back = new URL(referer).toString();
|
|
621
|
+
} catch {}
|
|
622
|
+
let status = 303;
|
|
623
|
+
let headers;
|
|
624
|
+
if (result instanceof Response) {
|
|
625
|
+
headers = new Headers(result.headers);
|
|
626
|
+
if (result.headers.has("Location")) {
|
|
627
|
+
headers.set("Location", new URL(result.headers.get("Location"), url.origin + base).toString());
|
|
628
|
+
if (validRedirectStatuses.has(result.status)) status = result.status;
|
|
629
|
+
} else {
|
|
630
|
+
headers.set("Location", back);
|
|
631
|
+
}
|
|
632
|
+
headers.delete("Content-Type");
|
|
633
|
+
headers.delete("Content-Length");
|
|
634
|
+
} else {
|
|
635
|
+
headers = new Headers({
|
|
636
|
+
Location: back
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
if (result && !(result instanceof Response)) {
|
|
640
|
+
headers.append("Set-Cookie", encodeFlashCookie(url.pathname + url.search, result, args, thrown));
|
|
641
|
+
}
|
|
642
|
+
return new Response(null, {
|
|
643
|
+
status,
|
|
644
|
+
headers
|
|
645
|
+
});
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
let defaultNoJSHandler;
|
|
649
|
+
function isFormPost(request) {
|
|
650
|
+
if (request.method !== "POST" || request.headers.has(BODY_FORMAT_HEADER)) return false;
|
|
651
|
+
const type = request.headers.get("content-type") || "";
|
|
652
|
+
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
653
|
+
}
|
|
474
654
|
function serializedResponse(value, headers, codec) {
|
|
475
655
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
|
|
476
656
|
headers.set("Content-Type", "text/plain");
|
|
@@ -528,6 +708,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
528
708
|
const provide = options.provideEvent || provideEvent;
|
|
529
709
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
530
710
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
711
|
+
const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
|
|
531
712
|
const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
|
|
532
713
|
const parsed = await parseArguments(request, url, instance, codec);
|
|
533
714
|
const headers = new Headers();
|
|
@@ -551,7 +732,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
551
732
|
response,
|
|
552
733
|
value
|
|
553
734
|
} = result;
|
|
554
|
-
if (!instance && !
|
|
735
|
+
if (!instance && !handleNoJS && response && response.body) {
|
|
555
736
|
return response;
|
|
556
737
|
}
|
|
557
738
|
if (response && response.headers) {
|
|
@@ -587,7 +768,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
587
768
|
});
|
|
588
769
|
}
|
|
589
770
|
if (!instance) {
|
|
590
|
-
if (
|
|
771
|
+
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
591
772
|
if (result instanceof Response) return result;
|
|
592
773
|
return encodeResult(result, headers, 200, codec);
|
|
593
774
|
}
|
|
@@ -639,13 +820,13 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
639
820
|
}
|
|
640
821
|
headers.set(ERROR_HEADER, "true");
|
|
641
822
|
if (!instance) {
|
|
642
|
-
if (
|
|
823
|
+
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
643
824
|
if (x instanceof Response) return x;
|
|
644
825
|
}
|
|
645
826
|
return encodeResult(x, headers, status, codec);
|
|
646
827
|
}
|
|
647
828
|
if (!instance) {
|
|
648
|
-
if (
|
|
829
|
+
if (handleNoJS) return handleNoJS(x, request, parsed, true);
|
|
649
830
|
const message = x instanceof Error ? x.message : String(x);
|
|
650
831
|
return new Response(process.env.NODE_ENV === "development" ? message : null, {
|
|
651
832
|
status: 500
|
|
@@ -657,4 +838,4 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
657
838
|
}
|
|
658
839
|
}
|
|
659
840
|
|
|
660
|
-
export { ERROR_HEADER, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, configureServerFunctionsServer, createServerReference, decodeErrorHeaderValue, decodeResponse, encodeErrorHeaderValue, getServerFunction, getServerFunctionMeta, getServerFunctionMetadata, handleServerFunctionRequest, isServerFunction, registerServerFunction, registerServerReference, subscribeFlightData, withMeta };
|
|
841
|
+
export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getServerFunction, getServerFunctionMeta, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, isServerFunction, registerServerFunction, registerServerReference, subscribeFlightData, withMeta };
|
package/types/client.d.ts
CHANGED
|
@@ -109,9 +109,9 @@ export function style(
|
|
|
109
109
|
export function getOwner(): unknown;
|
|
110
110
|
export function mergeProps(...sources: unknown[]): unknown;
|
|
111
111
|
export function dynamicProperty(props: unknown, key: string): unknown;
|
|
112
|
-
export function applyRef(
|
|
113
|
-
r: ((element:
|
|
114
|
-
element:
|
|
112
|
+
export function applyRef<T extends Element = Element>(
|
|
113
|
+
r: ((element: NoInfer<T>) => void) | ((element: NoInfer<T>) => void)[],
|
|
114
|
+
element: T
|
|
115
115
|
): void;
|
|
116
116
|
export function ref(
|
|
117
117
|
fn: () => ((element: Element) => void) | ((element: Element) => void)[],
|