@solidjs/web 2.0.0-beta.26 → 2.0.0-beta.27
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 +41 -78
- package/dist/server.js +42 -80
- 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 +33 -45
- package/frames/dist/server.js +33 -45
- 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 +15 -2
- package/server-functions/dist/client.js +13 -3
- package/server-functions/dist/server.cjs +161 -5
- package/server-functions/dist/server.js +155 -6
- package/types/client.d.ts +3 -3
- package/types/frames/serializer.d.ts +7 -1
- 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 +3 -0
- package/types/server-functions/flash.d.ts +38 -0
- package/types/server-functions/server.d.ts +79 -4
- package/types/server-functions/shared.d.ts +35 -0
- package/types-cjs/client.d.cts +3 -3
- package/types-cjs/frames/serializer.d.cts +7 -1
- 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 +3 -0
- package/types-cjs/server-functions/flash.d.cts +38 -0
- package/types-cjs/server-functions/server.d.cts +79 -4
- package/types-cjs/server-functions/shared.d.cts +35 -0
|
@@ -10,6 +10,7 @@ function isResponseEnvelope(value) {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
seroval.Feature.AggregateError | seroval.Feature.BigIntTypedArray;
|
|
13
|
+
const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : seroval.Feature.ErrorPrototypeStack;
|
|
13
14
|
const DEFAULT_WEB_PLUGINS = Object.freeze([web.AbortSignalPlugin,
|
|
14
15
|
web.CustomEventPlugin, web.DOMExceptionPlugin, web.EventPlugin,
|
|
15
16
|
web.FormDataPlugin, web.HeadersPlugin, web.ReadableStreamPlugin, web.RequestPlugin, web.ResponsePlugin, web.URLSearchParamsPlugin, web.URLPlugin]);
|
|
@@ -35,11 +36,13 @@ function serializeJSON(value, {
|
|
|
35
36
|
onError,
|
|
36
37
|
...codecOptions
|
|
37
38
|
}) {
|
|
39
|
+
const resolved = resolveCodecOptions(codecOptions);
|
|
38
40
|
return seroval.toCrossJSONStream(value, {
|
|
39
41
|
onParse,
|
|
40
42
|
onDone,
|
|
41
43
|
onError,
|
|
42
|
-
...
|
|
44
|
+
...resolved,
|
|
45
|
+
disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures()
|
|
43
46
|
});
|
|
44
47
|
}
|
|
45
48
|
function createJSONDeserializer(options) {
|
|
@@ -117,6 +120,18 @@ const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
|
117
120
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
118
121
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
119
122
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
123
|
+
const FLASH_COOKIE = "flash";
|
|
124
|
+
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
125
|
+
function hasFlashCookie(cookieHeader) {
|
|
126
|
+
return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
|
|
127
|
+
}
|
|
128
|
+
function matchFlashCookie(cookieHeader) {
|
|
129
|
+
const match = cookieHeader && cookieHeader.match(FLASH_MATCHER);
|
|
130
|
+
return match ? match[1] : undefined;
|
|
131
|
+
}
|
|
132
|
+
function clearFlashCookie() {
|
|
133
|
+
return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
|
|
134
|
+
}
|
|
120
135
|
const BodyFormat = {
|
|
121
136
|
Serialized: "0",
|
|
122
137
|
String: "1",
|
|
@@ -329,11 +344,61 @@ async function decodeResponse(response, codecOptions) {
|
|
|
329
344
|
return await extractBody(response, codecOptions === undefined ? codecConfig.codec : codecOptions);
|
|
330
345
|
}
|
|
331
346
|
|
|
347
|
+
function encodeInputValue(value) {
|
|
348
|
+
if (value instanceof FormData) return {
|
|
349
|
+
$f: [...value.entries()].filter(([, v]) => typeof v === "string")
|
|
350
|
+
};
|
|
351
|
+
if (value instanceof URLSearchParams) return {
|
|
352
|
+
$u: [...value.entries()]
|
|
353
|
+
};
|
|
354
|
+
return value;
|
|
355
|
+
}
|
|
356
|
+
function decodeInputValue(value) {
|
|
357
|
+
if (value && typeof value === "object") {
|
|
358
|
+
if (Array.isArray(value.$f)) {
|
|
359
|
+
const form = new FormData();
|
|
360
|
+
for (const [k, v] of value.$f) form.append(k, v);
|
|
361
|
+
return form;
|
|
362
|
+
}
|
|
363
|
+
if (Array.isArray(value.$u)) return new URLSearchParams(value.$u);
|
|
364
|
+
}
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
function encodeFlashCookie(url, result, input, thrown) {
|
|
368
|
+
const isError = result instanceof Error;
|
|
369
|
+
const payload = {
|
|
370
|
+
url,
|
|
371
|
+
result: isError ? result.message : result,
|
|
372
|
+
error: isError,
|
|
373
|
+
thrown: !!thrown,
|
|
374
|
+
input: input.map(encodeInputValue)
|
|
375
|
+
};
|
|
376
|
+
return `${FLASH_COOKIE}=${encodeURIComponent(JSON.stringify(payload))}; Secure; HttpOnly; Path=/`;
|
|
377
|
+
}
|
|
378
|
+
function decodeFlashCookie(cookieHeader) {
|
|
379
|
+
const match = matchFlashCookie(cookieHeader);
|
|
380
|
+
if (!match) return;
|
|
381
|
+
try {
|
|
382
|
+
const payload = JSON.parse(decodeURIComponent(match));
|
|
383
|
+
if (!payload || !payload.result) return;
|
|
384
|
+
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
385
|
+
return {
|
|
386
|
+
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
387
|
+
url: payload.url,
|
|
388
|
+
result: payload.thrown ? undefined : result,
|
|
389
|
+
error: payload.thrown ? result : undefined
|
|
390
|
+
};
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.error(error);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
332
396
|
const config = {
|
|
333
397
|
provideEvent: undefined,
|
|
334
398
|
collectFlightData: undefined,
|
|
335
399
|
transformResult: undefined,
|
|
336
400
|
transformDirectResult: undefined,
|
|
401
|
+
handleNoJS: undefined,
|
|
337
402
|
endpoint: "/_server"
|
|
338
403
|
};
|
|
339
404
|
function configureServerFunctionsServer({
|
|
@@ -341,6 +406,7 @@ function configureServerFunctionsServer({
|
|
|
341
406
|
collectFlightData,
|
|
342
407
|
transformResult,
|
|
343
408
|
transformDirectResult,
|
|
409
|
+
handleNoJS,
|
|
344
410
|
endpoint,
|
|
345
411
|
codec
|
|
346
412
|
} = {}) {
|
|
@@ -348,6 +414,7 @@ function configureServerFunctionsServer({
|
|
|
348
414
|
if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
|
|
349
415
|
if (transformResult !== undefined) config.transformResult = transformResult;
|
|
350
416
|
if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
|
|
417
|
+
if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
|
|
351
418
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
352
419
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
353
420
|
}
|
|
@@ -473,6 +540,87 @@ async function foldFlightData(hook, event, headers, outcome) {
|
|
|
473
540
|
data
|
|
474
541
|
};
|
|
475
542
|
}
|
|
543
|
+
function parseSetCookie(setCookie) {
|
|
544
|
+
const [pair, ...attributes] = setCookie.split(";");
|
|
545
|
+
const eq = pair.indexOf("=");
|
|
546
|
+
if (eq < 0) return undefined;
|
|
547
|
+
const parsed = {
|
|
548
|
+
name: pair.slice(0, eq).trim(),
|
|
549
|
+
value: pair.slice(eq + 1).trim()
|
|
550
|
+
};
|
|
551
|
+
for (const attribute of attributes) {
|
|
552
|
+
const attrEq = attribute.indexOf("=");
|
|
553
|
+
const key = (attrEq < 0 ? attribute : attribute.slice(0, attrEq)).trim().toLowerCase();
|
|
554
|
+
const value = attrEq < 0 ? "" : attribute.slice(attrEq + 1).trim();
|
|
555
|
+
if (key === "max-age") parsed.maxAge = Number(value);else if (key === "expires") parsed.expires = new Date(value);
|
|
556
|
+
}
|
|
557
|
+
return parsed;
|
|
558
|
+
}
|
|
559
|
+
function foldSetCookies(headers, setCookies) {
|
|
560
|
+
const folded = new Headers(headers);
|
|
561
|
+
if (!setCookies.length) return folded;
|
|
562
|
+
const cookies = {};
|
|
563
|
+
for (const pair of folded.get("cookie")?.split(";") ?? []) {
|
|
564
|
+
const eq = pair.indexOf("=");
|
|
565
|
+
if (eq > -1) cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
566
|
+
}
|
|
567
|
+
for (const setCookie of setCookies) {
|
|
568
|
+
const parsed = parseSetCookie(setCookie);
|
|
569
|
+
if (!parsed) continue;
|
|
570
|
+
if (parsed.maxAge != null && parsed.maxAge <= 0 || parsed.expires != null && parsed.expires.getTime() <= Date.now()) {
|
|
571
|
+
delete cookies[parsed.name];
|
|
572
|
+
} else {
|
|
573
|
+
cookies[parsed.name] = parsed.value;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
folded.delete("cookie");
|
|
577
|
+
const serialized = Object.entries(cookies).map(([name, value]) => `${name}=${value}`).join("; ");
|
|
578
|
+
if (serialized) folded.set("cookie", serialized);
|
|
579
|
+
return folded;
|
|
580
|
+
}
|
|
581
|
+
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
582
|
+
function createNoJSHandler({
|
|
583
|
+
base = ""
|
|
584
|
+
} = {}) {
|
|
585
|
+
return function handleNoJS(result, request, args, thrown) {
|
|
586
|
+
const url = new URL(request.url);
|
|
587
|
+
let back = new URL(base || "/", url.origin).toString();
|
|
588
|
+
try {
|
|
589
|
+
const referer = request.headers.get("referer");
|
|
590
|
+
if (referer) back = new URL(referer).toString();
|
|
591
|
+
} catch {}
|
|
592
|
+
let status = 303;
|
|
593
|
+
let headers;
|
|
594
|
+
if (result instanceof Response) {
|
|
595
|
+
headers = new Headers(result.headers);
|
|
596
|
+
if (result.headers.has("Location")) {
|
|
597
|
+
headers.set("Location", new URL(result.headers.get("Location"), url.origin + base).toString());
|
|
598
|
+
if (validRedirectStatuses.has(result.status)) status = result.status;
|
|
599
|
+
} else {
|
|
600
|
+
headers.set("Location", back);
|
|
601
|
+
}
|
|
602
|
+
headers.delete("Content-Type");
|
|
603
|
+
headers.delete("Content-Length");
|
|
604
|
+
} else {
|
|
605
|
+
headers = new Headers({
|
|
606
|
+
Location: back
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
if (result && !(result instanceof Response)) {
|
|
610
|
+
headers.append("Set-Cookie", encodeFlashCookie(url.pathname + url.search, result, args, thrown));
|
|
611
|
+
}
|
|
612
|
+
return new Response(null, {
|
|
613
|
+
status,
|
|
614
|
+
headers
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
let defaultNoJSHandler;
|
|
619
|
+
function isFormPost(request) {
|
|
620
|
+
if (request.method !== "POST" || request.headers.has(BODY_FORMAT_HEADER)) return false;
|
|
621
|
+
const type = request.headers.get("content-type") || "";
|
|
622
|
+
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
623
|
+
}
|
|
476
624
|
function serializedResponse(value, headers, codec) {
|
|
477
625
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
|
|
478
626
|
headers.set("Content-Type", "text/plain");
|
|
@@ -530,6 +678,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
530
678
|
const provide = options.provideEvent || provideEvent;
|
|
531
679
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
532
680
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
681
|
+
const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
|
|
533
682
|
const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
|
|
534
683
|
const parsed = await parseArguments(request, url, instance, codec);
|
|
535
684
|
const headers = new Headers();
|
|
@@ -553,7 +702,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
553
702
|
response,
|
|
554
703
|
value
|
|
555
704
|
} = result;
|
|
556
|
-
if (!instance && !
|
|
705
|
+
if (!instance && !handleNoJS && response && response.body) {
|
|
557
706
|
return response;
|
|
558
707
|
}
|
|
559
708
|
if (response && response.headers) {
|
|
@@ -589,7 +738,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
589
738
|
});
|
|
590
739
|
}
|
|
591
740
|
if (!instance) {
|
|
592
|
-
if (
|
|
741
|
+
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
593
742
|
if (result instanceof Response) return result;
|
|
594
743
|
return encodeResult(result, headers, 200, codec);
|
|
595
744
|
}
|
|
@@ -641,13 +790,13 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
641
790
|
}
|
|
642
791
|
headers.set(ERROR_HEADER, "true");
|
|
643
792
|
if (!instance) {
|
|
644
|
-
if (
|
|
793
|
+
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
645
794
|
if (x instanceof Response) return x;
|
|
646
795
|
}
|
|
647
796
|
return encodeResult(x, headers, status, codec);
|
|
648
797
|
}
|
|
649
798
|
if (!instance) {
|
|
650
|
-
if (
|
|
799
|
+
if (handleNoJS) return handleNoJS(x, request, parsed, true);
|
|
651
800
|
const message = x instanceof Error ? x.message : String(x);
|
|
652
801
|
return new Response(process.env.NODE_ENV === "development" ? message : null, {
|
|
653
802
|
status: 500
|
|
@@ -660,19 +809,26 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
660
809
|
}
|
|
661
810
|
|
|
662
811
|
exports.ERROR_HEADER = ERROR_HEADER;
|
|
812
|
+
exports.FLASH_COOKIE = FLASH_COOKIE;
|
|
663
813
|
exports.FUNCTION_HEADER = FUNCTION_HEADER;
|
|
664
814
|
exports.GET = GET;
|
|
665
815
|
exports.INSTANCE_HEADER = INSTANCE_HEADER;
|
|
666
816
|
exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
|
|
817
|
+
exports.clearFlashCookie = clearFlashCookie;
|
|
667
818
|
exports.configureServerFunctionsServer = configureServerFunctionsServer;
|
|
819
|
+
exports.createNoJSHandler = createNoJSHandler;
|
|
668
820
|
exports.createServerReference = createServerReference;
|
|
669
821
|
exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
|
|
822
|
+
exports.decodeFlashCookie = decodeFlashCookie;
|
|
670
823
|
exports.decodeResponse = decodeResponse;
|
|
671
824
|
exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
|
|
825
|
+
exports.encodeFlashCookie = encodeFlashCookie;
|
|
826
|
+
exports.foldSetCookies = foldSetCookies;
|
|
672
827
|
exports.getServerFunction = getServerFunction;
|
|
673
828
|
exports.getServerFunctionMeta = getServerFunctionMeta;
|
|
674
829
|
exports.getServerFunctionMetadata = getServerFunctionMetadata;
|
|
675
830
|
exports.handleServerFunctionRequest = handleServerFunctionRequest;
|
|
831
|
+
exports.hasFlashCookie = hasFlashCookie;
|
|
676
832
|
exports.isServerFunction = isServerFunction;
|
|
677
833
|
exports.registerServerFunction = registerServerFunction;
|
|
678
834
|
exports.registerServerReference = registerServerReference;
|
|
@@ -8,6 +8,7 @@ function isResponseEnvelope(value) {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
Feature.AggregateError | Feature.BigIntTypedArray;
|
|
11
|
+
const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : Feature.ErrorPrototypeStack;
|
|
11
12
|
const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
|
|
12
13
|
CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
|
|
13
14
|
FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin]);
|
|
@@ -33,11 +34,13 @@ function serializeJSON(value, {
|
|
|
33
34
|
onError,
|
|
34
35
|
...codecOptions
|
|
35
36
|
}) {
|
|
37
|
+
const resolved = resolveCodecOptions(codecOptions);
|
|
36
38
|
return toCrossJSONStream(value, {
|
|
37
39
|
onParse,
|
|
38
40
|
onDone,
|
|
39
41
|
onError,
|
|
40
|
-
...
|
|
42
|
+
...resolved,
|
|
43
|
+
disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures()
|
|
41
44
|
});
|
|
42
45
|
}
|
|
43
46
|
function createJSONDeserializer(options) {
|
|
@@ -115,6 +118,18 @@ const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
|
115
118
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
116
119
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
117
120
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
121
|
+
const FLASH_COOKIE = "flash";
|
|
122
|
+
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
123
|
+
function hasFlashCookie(cookieHeader) {
|
|
124
|
+
return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
|
|
125
|
+
}
|
|
126
|
+
function matchFlashCookie(cookieHeader) {
|
|
127
|
+
const match = cookieHeader && cookieHeader.match(FLASH_MATCHER);
|
|
128
|
+
return match ? match[1] : undefined;
|
|
129
|
+
}
|
|
130
|
+
function clearFlashCookie() {
|
|
131
|
+
return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
|
|
132
|
+
}
|
|
118
133
|
const BodyFormat = {
|
|
119
134
|
Serialized: "0",
|
|
120
135
|
String: "1",
|
|
@@ -327,11 +342,61 @@ async function decodeResponse(response, codecOptions) {
|
|
|
327
342
|
return await extractBody(response, codecOptions === undefined ? codecConfig.codec : codecOptions);
|
|
328
343
|
}
|
|
329
344
|
|
|
345
|
+
function encodeInputValue(value) {
|
|
346
|
+
if (value instanceof FormData) return {
|
|
347
|
+
$f: [...value.entries()].filter(([, v]) => typeof v === "string")
|
|
348
|
+
};
|
|
349
|
+
if (value instanceof URLSearchParams) return {
|
|
350
|
+
$u: [...value.entries()]
|
|
351
|
+
};
|
|
352
|
+
return value;
|
|
353
|
+
}
|
|
354
|
+
function decodeInputValue(value) {
|
|
355
|
+
if (value && typeof value === "object") {
|
|
356
|
+
if (Array.isArray(value.$f)) {
|
|
357
|
+
const form = new FormData();
|
|
358
|
+
for (const [k, v] of value.$f) form.append(k, v);
|
|
359
|
+
return form;
|
|
360
|
+
}
|
|
361
|
+
if (Array.isArray(value.$u)) return new URLSearchParams(value.$u);
|
|
362
|
+
}
|
|
363
|
+
return value;
|
|
364
|
+
}
|
|
365
|
+
function encodeFlashCookie(url, result, input, thrown) {
|
|
366
|
+
const isError = result instanceof Error;
|
|
367
|
+
const payload = {
|
|
368
|
+
url,
|
|
369
|
+
result: isError ? result.message : result,
|
|
370
|
+
error: isError,
|
|
371
|
+
thrown: !!thrown,
|
|
372
|
+
input: input.map(encodeInputValue)
|
|
373
|
+
};
|
|
374
|
+
return `${FLASH_COOKIE}=${encodeURIComponent(JSON.stringify(payload))}; Secure; HttpOnly; Path=/`;
|
|
375
|
+
}
|
|
376
|
+
function decodeFlashCookie(cookieHeader) {
|
|
377
|
+
const match = matchFlashCookie(cookieHeader);
|
|
378
|
+
if (!match) return;
|
|
379
|
+
try {
|
|
380
|
+
const payload = JSON.parse(decodeURIComponent(match));
|
|
381
|
+
if (!payload || !payload.result) return;
|
|
382
|
+
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
383
|
+
return {
|
|
384
|
+
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
385
|
+
url: payload.url,
|
|
386
|
+
result: payload.thrown ? undefined : result,
|
|
387
|
+
error: payload.thrown ? result : undefined
|
|
388
|
+
};
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.error(error);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
330
394
|
const config = {
|
|
331
395
|
provideEvent: undefined,
|
|
332
396
|
collectFlightData: undefined,
|
|
333
397
|
transformResult: undefined,
|
|
334
398
|
transformDirectResult: undefined,
|
|
399
|
+
handleNoJS: undefined,
|
|
335
400
|
endpoint: "/_server"
|
|
336
401
|
};
|
|
337
402
|
function configureServerFunctionsServer({
|
|
@@ -339,6 +404,7 @@ function configureServerFunctionsServer({
|
|
|
339
404
|
collectFlightData,
|
|
340
405
|
transformResult,
|
|
341
406
|
transformDirectResult,
|
|
407
|
+
handleNoJS,
|
|
342
408
|
endpoint,
|
|
343
409
|
codec
|
|
344
410
|
} = {}) {
|
|
@@ -346,6 +412,7 @@ function configureServerFunctionsServer({
|
|
|
346
412
|
if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
|
|
347
413
|
if (transformResult !== undefined) config.transformResult = transformResult;
|
|
348
414
|
if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
|
|
415
|
+
if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
|
|
349
416
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
350
417
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
351
418
|
}
|
|
@@ -471,6 +538,87 @@ async function foldFlightData(hook, event, headers, outcome) {
|
|
|
471
538
|
data
|
|
472
539
|
};
|
|
473
540
|
}
|
|
541
|
+
function parseSetCookie(setCookie) {
|
|
542
|
+
const [pair, ...attributes] = setCookie.split(";");
|
|
543
|
+
const eq = pair.indexOf("=");
|
|
544
|
+
if (eq < 0) return undefined;
|
|
545
|
+
const parsed = {
|
|
546
|
+
name: pair.slice(0, eq).trim(),
|
|
547
|
+
value: pair.slice(eq + 1).trim()
|
|
548
|
+
};
|
|
549
|
+
for (const attribute of attributes) {
|
|
550
|
+
const attrEq = attribute.indexOf("=");
|
|
551
|
+
const key = (attrEq < 0 ? attribute : attribute.slice(0, attrEq)).trim().toLowerCase();
|
|
552
|
+
const value = attrEq < 0 ? "" : attribute.slice(attrEq + 1).trim();
|
|
553
|
+
if (key === "max-age") parsed.maxAge = Number(value);else if (key === "expires") parsed.expires = new Date(value);
|
|
554
|
+
}
|
|
555
|
+
return parsed;
|
|
556
|
+
}
|
|
557
|
+
function foldSetCookies(headers, setCookies) {
|
|
558
|
+
const folded = new Headers(headers);
|
|
559
|
+
if (!setCookies.length) return folded;
|
|
560
|
+
const cookies = {};
|
|
561
|
+
for (const pair of folded.get("cookie")?.split(";") ?? []) {
|
|
562
|
+
const eq = pair.indexOf("=");
|
|
563
|
+
if (eq > -1) cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
564
|
+
}
|
|
565
|
+
for (const setCookie of setCookies) {
|
|
566
|
+
const parsed = parseSetCookie(setCookie);
|
|
567
|
+
if (!parsed) continue;
|
|
568
|
+
if (parsed.maxAge != null && parsed.maxAge <= 0 || parsed.expires != null && parsed.expires.getTime() <= Date.now()) {
|
|
569
|
+
delete cookies[parsed.name];
|
|
570
|
+
} else {
|
|
571
|
+
cookies[parsed.name] = parsed.value;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
folded.delete("cookie");
|
|
575
|
+
const serialized = Object.entries(cookies).map(([name, value]) => `${name}=${value}`).join("; ");
|
|
576
|
+
if (serialized) folded.set("cookie", serialized);
|
|
577
|
+
return folded;
|
|
578
|
+
}
|
|
579
|
+
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
580
|
+
function createNoJSHandler({
|
|
581
|
+
base = ""
|
|
582
|
+
} = {}) {
|
|
583
|
+
return function handleNoJS(result, request, args, thrown) {
|
|
584
|
+
const url = new URL(request.url);
|
|
585
|
+
let back = new URL(base || "/", url.origin).toString();
|
|
586
|
+
try {
|
|
587
|
+
const referer = request.headers.get("referer");
|
|
588
|
+
if (referer) back = new URL(referer).toString();
|
|
589
|
+
} catch {}
|
|
590
|
+
let status = 303;
|
|
591
|
+
let headers;
|
|
592
|
+
if (result instanceof Response) {
|
|
593
|
+
headers = new Headers(result.headers);
|
|
594
|
+
if (result.headers.has("Location")) {
|
|
595
|
+
headers.set("Location", new URL(result.headers.get("Location"), url.origin + base).toString());
|
|
596
|
+
if (validRedirectStatuses.has(result.status)) status = result.status;
|
|
597
|
+
} else {
|
|
598
|
+
headers.set("Location", back);
|
|
599
|
+
}
|
|
600
|
+
headers.delete("Content-Type");
|
|
601
|
+
headers.delete("Content-Length");
|
|
602
|
+
} else {
|
|
603
|
+
headers = new Headers({
|
|
604
|
+
Location: back
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
if (result && !(result instanceof Response)) {
|
|
608
|
+
headers.append("Set-Cookie", encodeFlashCookie(url.pathname + url.search, result, args, thrown));
|
|
609
|
+
}
|
|
610
|
+
return new Response(null, {
|
|
611
|
+
status,
|
|
612
|
+
headers
|
|
613
|
+
});
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
let defaultNoJSHandler;
|
|
617
|
+
function isFormPost(request) {
|
|
618
|
+
if (request.method !== "POST" || request.headers.has(BODY_FORMAT_HEADER)) return false;
|
|
619
|
+
const type = request.headers.get("content-type") || "";
|
|
620
|
+
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
621
|
+
}
|
|
474
622
|
function serializedResponse(value, headers, codec) {
|
|
475
623
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
|
|
476
624
|
headers.set("Content-Type", "text/plain");
|
|
@@ -528,6 +676,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
528
676
|
const provide = options.provideEvent || provideEvent;
|
|
529
677
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
530
678
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
679
|
+
const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
|
|
531
680
|
const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
|
|
532
681
|
const parsed = await parseArguments(request, url, instance, codec);
|
|
533
682
|
const headers = new Headers();
|
|
@@ -551,7 +700,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
551
700
|
response,
|
|
552
701
|
value
|
|
553
702
|
} = result;
|
|
554
|
-
if (!instance && !
|
|
703
|
+
if (!instance && !handleNoJS && response && response.body) {
|
|
555
704
|
return response;
|
|
556
705
|
}
|
|
557
706
|
if (response && response.headers) {
|
|
@@ -587,7 +736,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
587
736
|
});
|
|
588
737
|
}
|
|
589
738
|
if (!instance) {
|
|
590
|
-
if (
|
|
739
|
+
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
591
740
|
if (result instanceof Response) return result;
|
|
592
741
|
return encodeResult(result, headers, 200, codec);
|
|
593
742
|
}
|
|
@@ -639,13 +788,13 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
639
788
|
}
|
|
640
789
|
headers.set(ERROR_HEADER, "true");
|
|
641
790
|
if (!instance) {
|
|
642
|
-
if (
|
|
791
|
+
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
643
792
|
if (x instanceof Response) return x;
|
|
644
793
|
}
|
|
645
794
|
return encodeResult(x, headers, status, codec);
|
|
646
795
|
}
|
|
647
796
|
if (!instance) {
|
|
648
|
-
if (
|
|
797
|
+
if (handleNoJS) return handleNoJS(x, request, parsed, true);
|
|
649
798
|
const message = x instanceof Error ? x.message : String(x);
|
|
650
799
|
return new Response(process.env.NODE_ENV === "development" ? message : null, {
|
|
651
800
|
status: 500
|
|
@@ -657,4 +806,4 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
657
806
|
}
|
|
658
807
|
}
|
|
659
808
|
|
|
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 };
|
|
809
|
+
export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, 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)[],
|
|
@@ -37,7 +37,9 @@ export interface WebSerializerOptions {
|
|
|
37
37
|
scopeId?: string;
|
|
38
38
|
/**
|
|
39
39
|
* Seroval feature bitflags to exclude from output. Defaults to disabling
|
|
40
|
-
* post-ES2017 features (AggregateError, BigInt typed arrays).
|
|
40
|
+
* post-ES2017 features (AggregateError, BigInt typed arrays). Outside
|
|
41
|
+
* development, `Error.prototype.stack` is additionally stripped on top of
|
|
42
|
+
* any override — serialized stacks leak server paths to the client.
|
|
41
43
|
*/
|
|
42
44
|
disabledFeatures?: number;
|
|
43
45
|
/** Extra plugins, composed ahead of `DEFAULT_WEB_PLUGINS`. */
|
|
@@ -101,6 +103,10 @@ export interface JSONCodecOptions {
|
|
|
101
103
|
/**
|
|
102
104
|
* Seroval feature bitflags to exclude. Defaults to disabling `RegExp`
|
|
103
105
|
* (payloads may come from an untrusted peer). Must match on both peers.
|
|
106
|
+
* Outside development, the encoding side additionally strips
|
|
107
|
+
* `Error.prototype.stack` on top of any override — serialized stacks leak
|
|
108
|
+
* server paths to the client. Decoding stays permissive, so payloads from
|
|
109
|
+
* a development peer still round-trip.
|
|
104
110
|
*/
|
|
105
111
|
disabledFeatures?: number;
|
|
106
112
|
/** Maximum parse/deserialize depth. Defaults to 64. Must match on both peers. */
|
package/types/jsx.d.ts
CHANGED
|
@@ -240,7 +240,7 @@ export namespace JSX {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
type RefCallback<T> = (el: T) => void;
|
|
243
|
-
type Ref<T> = T | RefCallback<T> |
|
|
243
|
+
type Ref<T> = T | RefCallback<T> | Ref<T>[];
|
|
244
244
|
|
|
245
245
|
interface IntrinsicAttributes {
|
|
246
246
|
ref?: Ref<unknown> | undefined;
|
package/types/response.d.ts
CHANGED
|
@@ -49,6 +49,16 @@ export interface Href {
|
|
|
49
49
|
*/
|
|
50
50
|
export function isHref(value: unknown): value is Href;
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Response header naming the cache keys a mutation invalidated
|
|
54
|
+
* (`"X-Revalidate"`), comma separated. The response helpers below set it
|
|
55
|
+
* from their `revalidate` option; the client transport treats its presence
|
|
56
|
+
* as control flow, and integrations read it to invalidate their own cache.
|
|
57
|
+
* Core never inspects the keys, so how they are matched (prefixes, exact
|
|
58
|
+
* names, namespaces) is the integration's business.
|
|
59
|
+
*/
|
|
60
|
+
export const REVALIDATE_HEADER: string;
|
|
61
|
+
|
|
52
62
|
/** `ResponseInit` accepted by the response helpers, plus `revalidate`. */
|
|
53
63
|
export interface ResponseHelperInit extends ResponseInit {
|
|
54
64
|
/**
|
package/types/serializer.d.ts
CHANGED
|
@@ -37,7 +37,9 @@ export interface WebSerializerOptions {
|
|
|
37
37
|
scopeId?: string;
|
|
38
38
|
/**
|
|
39
39
|
* Seroval feature bitflags to exclude from output. Defaults to disabling
|
|
40
|
-
* post-ES2017 features (AggregateError, BigInt typed arrays).
|
|
40
|
+
* post-ES2017 features (AggregateError, BigInt typed arrays). Outside
|
|
41
|
+
* development, `Error.prototype.stack` is additionally stripped on top of
|
|
42
|
+
* any override — serialized stacks leak server paths to the client.
|
|
41
43
|
*/
|
|
42
44
|
disabledFeatures?: number;
|
|
43
45
|
/** Extra plugins, composed ahead of `DEFAULT_WEB_PLUGINS`. */
|
|
@@ -101,6 +103,10 @@ export interface JSONCodecOptions {
|
|
|
101
103
|
/**
|
|
102
104
|
* Seroval feature bitflags to exclude. Defaults to disabling `RegExp`
|
|
103
105
|
* (payloads may come from an untrusted peer). Must match on both peers.
|
|
106
|
+
* Outside development, the encoding side additionally strips
|
|
107
|
+
* `Error.prototype.stack` on top of any override — serialized stacks leak
|
|
108
|
+
* server paths to the client. Decoding stays permissive, so payloads from
|
|
109
|
+
* a development peer still round-trip.
|
|
104
110
|
*/
|
|
105
111
|
disabledFeatures?: number;
|
|
106
112
|
/** Maximum parse/deserialize depth. Defaults to 64. Must match on both peers. */
|
|
@@ -3,13 +3,16 @@ import { ServerFunction, ServerFunctionMetadata } from "./shared.js";
|
|
|
3
3
|
|
|
4
4
|
export {
|
|
5
5
|
ERROR_HEADER,
|
|
6
|
+
FLASH_COOKIE,
|
|
6
7
|
FUNCTION_HEADER,
|
|
7
8
|
INSTANCE_HEADER,
|
|
8
9
|
SINGLE_FLIGHT_HEADER,
|
|
10
|
+
clearFlashCookie,
|
|
9
11
|
decodeErrorHeaderValue,
|
|
10
12
|
decodeResponse,
|
|
11
13
|
encodeErrorHeaderValue,
|
|
12
14
|
getServerFunctionMetadata,
|
|
15
|
+
hasFlashCookie,
|
|
13
16
|
isServerFunction,
|
|
14
17
|
subscribeFlightData,
|
|
15
18
|
withMeta
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The outcome of a call made without the client runtime, as it rides the
|
|
3
|
+
* flash cookie: what was submitted, where, and what came back. `result` and
|
|
4
|
+
* `error` are mutually exclusive — a thrown outcome fills `error`, a
|
|
5
|
+
* returned one fills `result` — mirroring the split a scripted call sees.
|
|
6
|
+
*/
|
|
7
|
+
export interface FlashSubmission {
|
|
8
|
+
/** The arguments the call was made with (files are dropped). */
|
|
9
|
+
input: any[];
|
|
10
|
+
/** The call's url: pathname + search of the server function request. */
|
|
11
|
+
url: string;
|
|
12
|
+
/** The returned value, when the call returned. */
|
|
13
|
+
result?: any;
|
|
14
|
+
/** The thrown value, when the call threw. */
|
|
15
|
+
error?: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Encodes the outcome of a no-JS call as a `Set-Cookie` value, for the
|
|
20
|
+
* handler to send with its redirect. `url` identifies which submission the
|
|
21
|
+
* outcome belongs to; pass `thrown` when the call threw rather than
|
|
22
|
+
* returned.
|
|
23
|
+
*
|
|
24
|
+
* The payload is JSON inside the cookie: `FormData` and `URLSearchParams`
|
|
25
|
+
* arguments are captured as entry pairs and revived on decode, and `File`
|
|
26
|
+
* entries are dropped (they cannot ride a cookie). Keep in mind the 4 KB
|
|
27
|
+
* cookie budget — outcomes larger than that will not survive the round
|
|
28
|
+
* trip.
|
|
29
|
+
*/
|
|
30
|
+
export function encodeFlashCookie(url: string, result: any, input: any[], thrown?: boolean): string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Decodes the flash cookie out of a request's `Cookie` header, for the
|
|
34
|
+
* render that follows the redirect. Returns undefined when the cookie is
|
|
35
|
+
* absent or unreadable — a malformed cookie never takes down the render,
|
|
36
|
+
* and `clearFlashCookie` should be appended regardless.
|
|
37
|
+
*/
|
|
38
|
+
export function decodeFlashCookie(cookieHeader: string | null): FlashSubmission | undefined;
|