@solidjs/web 2.0.0-beta.25 → 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 +14 -9
- package/dist/dev.js +14 -10
- package/dist/server.cjs +41 -78
- package/dist/server.js +42 -80
- package/dist/web.cjs +14 -9
- package/dist/web.js +14 -10
- package/frames/dist/client.cjs +180 -224
- package/frames/dist/client.dev.cjs +1456 -0
- package/frames/dist/client.dev.js +1444 -0
- package/frames/dist/client.js +181 -225
- package/frames/dist/server.cjs +139 -123
- package/frames/dist/server.js +139 -123
- package/package.json +24 -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 +18 -5
- package/server-functions/dist/client.js +16 -6
- package/server-functions/dist/server.cjs +164 -8
- package/server-functions/dist/server.js +158 -9
- package/types/client.d.ts +3 -3
- package/types/frames/client.d.ts +1 -1
- package/types/frames/frame-client.d.ts +34 -28
- 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/client.d.cts +1 -1
- package/types-cjs/frames/frame-client.d.cts +34 -28
- 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
|
@@ -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",
|
|
@@ -247,15 +262,15 @@ class ChunkReader {
|
|
|
247
262
|
}
|
|
248
263
|
}
|
|
249
264
|
async next() {
|
|
250
|
-
|
|
265
|
+
while (this.buffer.length < 12) {
|
|
251
266
|
if (this.done) {
|
|
252
|
-
return {
|
|
267
|
+
if (this.buffer.length === 0) return {
|
|
253
268
|
done: true,
|
|
254
269
|
value: undefined
|
|
255
270
|
};
|
|
271
|
+
throw new Error("Malformed server function stream.");
|
|
256
272
|
}
|
|
257
273
|
await this.readChunk();
|
|
258
|
-
return await this.next();
|
|
259
274
|
}
|
|
260
275
|
const head = new TextDecoder().decode(this.buffer.subarray(1, 11));
|
|
261
276
|
const bytes = Number.parseInt(head, 16);
|
|
@@ -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)[],
|
package/types/frames/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createFrame, createFrameHost,
|
|
1
|
+
export { createFrame, createFrameHost, createFrameElement, FRAME_APPLIED_EVENT } from "./frame-client.js";
|
|
2
2
|
export { FRAME_STREAM_HEADER, applyFrameResponse, isFrameStreamResponse, createServerComponentHandler } from "./frame-transport.js";
|
|
3
3
|
export { createJSONDataTable } from "./serializer.js";
|
|
4
4
|
export declare function getFrameHost(): any;
|
|
@@ -41,15 +41,6 @@ export type FrameChunk =
|
|
|
41
41
|
inlineStyles?: { id: string; content?: string; attrs?: Record<string, string> }[];
|
|
42
42
|
}
|
|
43
43
|
| { type: "slot"; id: string; version: number; key: string; args: Record<string, unknown> }
|
|
44
|
-
| { type: "template"; id: string; version: number; key: string; html: string; fields: string[] }
|
|
45
|
-
| {
|
|
46
|
-
type: "block";
|
|
47
|
-
id: string;
|
|
48
|
-
version: number;
|
|
49
|
-
key: string;
|
|
50
|
-
template: string;
|
|
51
|
-
values: unknown[];
|
|
52
|
-
}
|
|
53
44
|
| { type: "complete"; id: string; version: number }
|
|
54
45
|
| { type: "error"; id: string; version: number; key?: string; error: unknown };
|
|
55
46
|
|
|
@@ -170,7 +161,7 @@ export interface FrameHostOptions {
|
|
|
170
161
|
|
|
171
162
|
export function createFrameHost(options?: FrameHostOptions): FrameHost;
|
|
172
163
|
|
|
173
|
-
/** Options for `createFrame` / `
|
|
164
|
+
/** Options for `createFrame` / `createFrameElement`. */
|
|
174
165
|
export interface FrameOptions {
|
|
175
166
|
/** Register with this host under `id`, receiving routed/buffered chunks. */
|
|
176
167
|
host?: FrameHost;
|
|
@@ -194,29 +185,44 @@ export interface FrameOptions {
|
|
|
194
185
|
* streamed chunks).
|
|
195
186
|
*/
|
|
196
187
|
ownerScope?<T>(fn: () => T): T;
|
|
188
|
+
/**
|
|
189
|
+
* Boundary-driven segment reveal. When present, `#revealSegment` hands the
|
|
190
|
+
* placeholder seam to this hook instead of swapping imperatively: the binding
|
|
191
|
+
* reconstructs a client `<Loading>` there — `fallback` is the placeholder's
|
|
192
|
+
* own template content (shown while holding), `content()` materializes the
|
|
193
|
+
* segment and renders its client fills INSIDE the boundary so their readiness
|
|
194
|
+
* gates the reveal — and inserts it before `before`. An unboundaried async
|
|
195
|
+
* fill suspends up to that boundary and is covered instead of orphaned; one
|
|
196
|
+
* boundary per revealed segment, i.e. per author-placed `<Loading>`. Omit it
|
|
197
|
+
* for the framework-agnostic imperative swap (no reactive reveal).
|
|
198
|
+
*/
|
|
199
|
+
reveal?(seam: { before: Node; fallback: Node[]; content: () => Node | DocumentFragment }): void;
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
/**
|
|
202
|
+
/**
|
|
203
|
+
* A frame rendering into an EXISTING element boundary. Pass `adopt: true` for
|
|
204
|
+
* the document-SSR path: the element already holds server-rendered content,
|
|
205
|
+
* so the first apply morphs against it and slots sync immediately (hydration
|
|
206
|
+
* attach), claiming their server-rendered DOM — a document boot needs no
|
|
207
|
+
* chunk.
|
|
208
|
+
*/
|
|
200
209
|
export function createFrame(boundary: Element, options?: FrameOptions): Frame;
|
|
201
210
|
|
|
211
|
+
/** The default boundary/region element tag and its id attribute — the DOM
|
|
212
|
+
* contract the producer emits at t=0 and the consumer creates/adopts. */
|
|
213
|
+
export const FRAME_TAG: "dx-frame";
|
|
214
|
+
export const FRAME_ID_ATTR: "data-fid";
|
|
215
|
+
|
|
202
216
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* cleanup).
|
|
217
|
+
* Create a boundary/region ELEMENT and bind a host-registered frame to it.
|
|
218
|
+
* The frame mounts INTO the element (server content is its children, morphed
|
|
219
|
+
* in place). Because the boundary is a real node, `insert` places the
|
|
220
|
+
* returned `element` in any position — single, array, or fragment — with no
|
|
221
|
+
* special-casing. One frame per element; lifecycle belongs to the creator via
|
|
222
|
+
* `dispose()` (register it with your owner's cleanup).
|
|
209
223
|
*/
|
|
210
|
-
export function
|
|
211
|
-
readonly
|
|
224
|
+
export function createFrameElement(options: FrameOptions): {
|
|
225
|
+
readonly element: Element;
|
|
226
|
+
readonly frame: Frame;
|
|
212
227
|
dispose(): void;
|
|
213
228
|
};
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Binds a frame to an EXISTING marker range — the document-SSR adoption
|
|
217
|
-
* path: the page already holds the server-rendered boundary between
|
|
218
|
-
* `frame:<id>:start`/`:end` comments; the frame constructed over it treats
|
|
219
|
-
* that content as its own (first stream morphs rather than materializes)
|
|
220
|
-
* and slots sync immediately, claiming their server-rendered DOM.
|
|
221
|
-
*/
|
|
222
|
-
export function adoptFrameRange(start: Comment, end: Comment, options?: FrameOptions): Frame;
|
|
@@ -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;
|
|
@@ -4,13 +4,16 @@ import { RequestEvent } from "../server.js";
|
|
|
4
4
|
|
|
5
5
|
export {
|
|
6
6
|
ERROR_HEADER,
|
|
7
|
+
FLASH_COOKIE,
|
|
7
8
|
FUNCTION_HEADER,
|
|
8
9
|
INSTANCE_HEADER,
|
|
9
10
|
SINGLE_FLIGHT_HEADER,
|
|
11
|
+
clearFlashCookie,
|
|
10
12
|
decodeErrorHeaderValue,
|
|
11
13
|
decodeResponse,
|
|
12
14
|
encodeErrorHeaderValue,
|
|
13
15
|
getServerFunctionMetadata,
|
|
16
|
+
hasFlashCookie,
|
|
14
17
|
isServerFunction,
|
|
15
18
|
subscribeFlightData,
|
|
16
19
|
withMeta
|
|
@@ -22,6 +25,8 @@ export type {
|
|
|
22
25
|
ServerFunctionMetadata,
|
|
23
26
|
SingleFlightPayload
|
|
24
27
|
} from "./shared.js";
|
|
28
|
+
export { decodeFlashCookie, encodeFlashCookie } from "./flash.js";
|
|
29
|
+
export type { FlashSubmission } from "./flash.js";
|
|
25
30
|
import { ServerFunction } from "./shared.js";
|
|
26
31
|
|
|
27
32
|
/**
|
|
@@ -85,6 +90,57 @@ export type CollectFlightDataHook = (
|
|
|
85
90
|
outcome: ServerFunctionOutcome
|
|
86
91
|
) => unknown | Promise<unknown>;
|
|
87
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Request headers with `setCookies` folded into the `Cookie` header, as the
|
|
95
|
+
* browser would have applied them before its next request. Later entries
|
|
96
|
+
* win on conflict, and deletions are honored (`Max-Age` at or below zero,
|
|
97
|
+
* `Expires` in the past). The input headers are not modified.
|
|
98
|
+
*
|
|
99
|
+
* For work re-run on the server after a mutation — a
|
|
100
|
+
* `CollectFlightDataHook` gathering fresh data, typically. That pass starts
|
|
101
|
+
* from the request that triggered the mutation, whose cookies are
|
|
102
|
+
* pre-mutation by definition, so a read depending on a session the mutation
|
|
103
|
+
* just established would otherwise see the old state. Which responses
|
|
104
|
+
* contribute their `Set-Cookie`s, and in what order, is the caller's
|
|
105
|
+
* decision.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const headers = foldSetCookies(event.request.headers, [
|
|
110
|
+
* ...(event.response?.headers?.getSetCookie() ?? []),
|
|
111
|
+
* ...(outcome.response?.headers?.getSetCookie() ?? [])
|
|
112
|
+
* ]);
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function foldSetCookies(headers: Headers, setCookies: readonly string[]): Headers;
|
|
116
|
+
|
|
117
|
+
/** Options for `createNoJSHandler`. */
|
|
118
|
+
export interface NoJSHandlerOptions {
|
|
119
|
+
/** The app's mount path, for resolving a relative redirect `Location`. */
|
|
120
|
+
base?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Builds the `handleNoJS` implementation for the no-JS form convention: a
|
|
125
|
+
* form posted without the client runtime has no way to receive a value, so
|
|
126
|
+
* the call redirects back to the referring page (or to the result's own
|
|
127
|
+
* `Location`, resolved against `base`) with the outcome riding a one-shot
|
|
128
|
+
* flash cookie. `303 See Other` turns the POST into a GET unless the result
|
|
129
|
+
* names a redirect status of its own. A result that is already a `Response`
|
|
130
|
+
* carries its meaning in its metadata and is not flashed.
|
|
131
|
+
*
|
|
132
|
+
* The render that follows reads the cookie with `decodeFlashCookie` and
|
|
133
|
+
* surfaces the outcome however it likes — that half is the integration's.
|
|
134
|
+
*
|
|
135
|
+
* The handler applies to every call it receives. `handleServerFunctionRequest`
|
|
136
|
+
* already uses it for browser form posts, so wire it explicitly only to set
|
|
137
|
+
* a `base`, or to extend the convention to direct HTTP calls by registering
|
|
138
|
+
* it through `configureServerFunctionsServer`.
|
|
139
|
+
*/
|
|
140
|
+
export function createNoJSHandler(
|
|
141
|
+
options?: NoJSHandlerOptions
|
|
142
|
+
): (result: unknown, request: Request, args: unknown[], thrown?: boolean) => Response;
|
|
143
|
+
|
|
88
144
|
/** Options for `configureServerFunctionsServer`. */
|
|
89
145
|
export interface ServerFunctionsServerConfig {
|
|
90
146
|
/**
|
|
@@ -119,6 +175,23 @@ export interface ServerFunctionsServerConfig {
|
|
|
119
175
|
* calls during document SSR — e.g. frames' `frameTransformDirectResult`.
|
|
120
176
|
*/
|
|
121
177
|
transformDirectResult?(value: unknown, options: { id: string }): unknown;
|
|
178
|
+
/**
|
|
179
|
+
* Server-wide response builder for calls made without the client runtime
|
|
180
|
+
* (see `handleNoJS` in `HandleServerFunctionRequestOptions`); a
|
|
181
|
+
* per-request option overrides it. Set it to `createNoJSHandler({ base })`
|
|
182
|
+
* to apply the convention to every non-scripted call rather than only to
|
|
183
|
+
* browser form posts, to a handler of your own to replace it, or to
|
|
184
|
+
* `null` to disable the built-in convention and answer form posts with
|
|
185
|
+
* the plain serialized response.
|
|
186
|
+
*/
|
|
187
|
+
handleNoJS?:
|
|
188
|
+
| ((
|
|
189
|
+
result: unknown,
|
|
190
|
+
request: Request,
|
|
191
|
+
args: unknown[],
|
|
192
|
+
thrown?: boolean
|
|
193
|
+
) => Response | Promise<Response>)
|
|
194
|
+
| null;
|
|
122
195
|
/**
|
|
123
196
|
* Endpoint the HTTP handler is mounted on, used for the `url` of SSR'd
|
|
124
197
|
* references (e.g. form actions) — must match the client configuration.
|
|
@@ -285,11 +358,13 @@ export interface HandleServerFunctionOptions {
|
|
|
285
358
|
collectFlightData?: CollectFlightDataHook;
|
|
286
359
|
/**
|
|
287
360
|
* Builds the response for calls made without the client runtime (no
|
|
288
|
-
* instance header — no-JS form posts, direct HTTP)
|
|
289
|
-
* point for conventions like redirect-with-flash-cookie. Receives the
|
|
361
|
+
* instance header — no-JS form posts, direct HTTP). Receives the
|
|
290
362
|
* (transformed) result, the request, and the decoded arguments; `thrown`
|
|
291
|
-
* is set when the result was thrown rather than returned.
|
|
292
|
-
*
|
|
363
|
+
* is set when the result was thrown rather than returned.
|
|
364
|
+
*
|
|
365
|
+
* Overrides the configured hook, which in turn overrides the built-in
|
|
366
|
+
* `createNoJSHandler()` applied to browser form posts. Other
|
|
367
|
+
* no-instance callers get the normal serialized response.
|
|
293
368
|
*/
|
|
294
369
|
handleNoJS?(
|
|
295
370
|
result: unknown,
|