@solidjs/web 2.0.0-rc.1 → 2.0.0-rc.2
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 +31 -6
- package/dist/dev.js +30 -7
- package/dist/server.cjs +353 -93
- package/dist/server.js +346 -97
- package/dist/web.cjs +31 -6
- package/dist/web.js +30 -7
- package/frames/dist/client.cjs +93 -7
- package/frames/dist/client.dev.cjs +96 -7
- package/frames/dist/client.dev.js +97 -8
- package/frames/dist/client.js +94 -8
- package/frames/dist/server.cjs +259 -138
- package/frames/dist/server.js +260 -139
- package/package.json +3 -3
- package/serialization/dist/decode.cjs +19 -2
- package/serialization/dist/decode.js +20 -3
- package/serialization/dist/serialization.cjs +19 -2
- package/serialization/dist/serialization.js +20 -3
- package/server-functions/dist/client.cjs +48 -7
- package/server-functions/dist/client.js +48 -8
- package/server-functions/dist/server.cjs +76 -5
- package/server-functions/dist/server.dev.cjs +76 -5
- package/server-functions/dist/server.dev.js +76 -6
- package/server-functions/dist/server.js +76 -6
- package/types/core.d.ts +3 -0
- package/types/frames/frame-client.d.ts +18 -0
- package/types/server-functions/client.d.ts +33 -2
- package/types/server-functions/server.d.ts +67 -2
- package/types/server-mock.d.ts +11 -2
- package/types/server.d.ts +23 -2
- package/types-cjs/core.d.cts +3 -0
- package/types-cjs/frames/frame-client.d.cts +18 -0
- package/types-cjs/server-functions/client.d.cts +33 -2
- package/types-cjs/server-functions/server.d.cts +67 -2
- package/types-cjs/server-mock.d.cts +11 -2
- package/types-cjs/server.d.cts +23 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/web",
|
|
3
3
|
"description": "Solid's web runtime: client rendering, hydration, SSR, and DOM-specific control flow (Portal, Dynamic).",
|
|
4
|
-
"version": "2.0.0-rc.
|
|
4
|
+
"version": "2.0.0-rc.2",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://solidjs.com",
|
|
@@ -378,11 +378,11 @@
|
|
|
378
378
|
"seroval-plugins": "~1.5.4"
|
|
379
379
|
},
|
|
380
380
|
"peerDependencies": {
|
|
381
|
-
"solid-js": "^2.0.0-rc.
|
|
381
|
+
"solid-js": "^2.0.0-rc.2"
|
|
382
382
|
},
|
|
383
383
|
"devDependencies": {
|
|
384
384
|
"@codspeed/vitest-plugin": "^5.4.0",
|
|
385
|
-
"solid-js": "2.0.0-rc.
|
|
385
|
+
"solid-js": "2.0.0-rc.2"
|
|
386
386
|
},
|
|
387
387
|
"scripts": {
|
|
388
388
|
"build": "npm-run-all -nl build:clean types:copy-jsx build:js",
|
|
@@ -8,6 +8,9 @@ const state = globalThis[STATE] || (globalThis[STATE] = {
|
|
|
8
8
|
materialized: new WeakMap(),
|
|
9
9
|
materializedValues: new WeakSet()
|
|
10
10
|
});
|
|
11
|
+
function setContainerTraceStreamMint(fn) {
|
|
12
|
+
state.streamOf = fn;
|
|
13
|
+
}
|
|
11
14
|
const TRACE = Symbol.for("dom-expressions.container-trace");
|
|
12
15
|
function materialize(marker) {
|
|
13
16
|
let value = state.materialized.get(marker.$tr);
|
|
@@ -20,9 +23,10 @@ function materialize(marker) {
|
|
|
20
23
|
}
|
|
21
24
|
function parseTrace(value, ctx) {
|
|
22
25
|
const trace = value[TRACE];
|
|
26
|
+
const sub = trace.subscribe();
|
|
23
27
|
return {
|
|
24
28
|
a: trace.array ? 1 : 0,
|
|
25
|
-
i: ctx.parse(
|
|
29
|
+
i: ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
32
|
const ContainerTracePlugin = {
|
|
@@ -36,9 +40,10 @@ const ContainerTracePlugin = {
|
|
|
36
40
|
},
|
|
37
41
|
async async(value, ctx) {
|
|
38
42
|
const trace = value[TRACE];
|
|
43
|
+
const sub = trace.subscribe();
|
|
39
44
|
return {
|
|
40
45
|
a: trace.array ? 1 : 0,
|
|
41
|
-
i: await ctx.parse(
|
|
46
|
+
i: await ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
|
|
42
47
|
};
|
|
43
48
|
},
|
|
44
49
|
stream: parseTrace
|
|
@@ -56,6 +61,18 @@ const ContainerTracePlugin = {
|
|
|
56
61
|
}
|
|
57
62
|
};
|
|
58
63
|
|
|
64
|
+
setContainerTraceStreamMint(iterable => {
|
|
65
|
+
const stream = seroval.createStream();
|
|
66
|
+
(async () => {
|
|
67
|
+
try {
|
|
68
|
+
for await (const value of iterable) stream.next(value);
|
|
69
|
+
stream.return(undefined);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
stream.throw(error);
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
return stream;
|
|
75
|
+
});
|
|
59
76
|
const DEFAULT_WEB_PLUGINS = Object.freeze([web.AbortSignalPlugin,
|
|
60
77
|
web.CustomEventPlugin, web.DOMExceptionPlugin, web.EventPlugin,
|
|
61
78
|
web.FormDataPlugin, web.HeadersPlugin, web.ReadableStreamPlugin, web.RequestPlugin, web.ResponsePlugin, web.URLSearchParamsPlugin, web.URLPlugin,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Feature, fromCrossJSON } from 'seroval';
|
|
1
|
+
import { Feature, fromCrossJSON, createStream } from 'seroval';
|
|
2
2
|
import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
|
|
3
3
|
|
|
4
4
|
const STATE = Symbol.for("dom-expressions.container-trace-state");
|
|
@@ -6,6 +6,9 @@ const state = globalThis[STATE] || (globalThis[STATE] = {
|
|
|
6
6
|
materialized: new WeakMap(),
|
|
7
7
|
materializedValues: new WeakSet()
|
|
8
8
|
});
|
|
9
|
+
function setContainerTraceStreamMint(fn) {
|
|
10
|
+
state.streamOf = fn;
|
|
11
|
+
}
|
|
9
12
|
const TRACE = Symbol.for("dom-expressions.container-trace");
|
|
10
13
|
function materialize(marker) {
|
|
11
14
|
let value = state.materialized.get(marker.$tr);
|
|
@@ -18,9 +21,10 @@ function materialize(marker) {
|
|
|
18
21
|
}
|
|
19
22
|
function parseTrace(value, ctx) {
|
|
20
23
|
const trace = value[TRACE];
|
|
24
|
+
const sub = trace.subscribe();
|
|
21
25
|
return {
|
|
22
26
|
a: trace.array ? 1 : 0,
|
|
23
|
-
i: ctx.parse(
|
|
27
|
+
i: ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
|
|
24
28
|
};
|
|
25
29
|
}
|
|
26
30
|
const ContainerTracePlugin = {
|
|
@@ -34,9 +38,10 @@ const ContainerTracePlugin = {
|
|
|
34
38
|
},
|
|
35
39
|
async async(value, ctx) {
|
|
36
40
|
const trace = value[TRACE];
|
|
41
|
+
const sub = trace.subscribe();
|
|
37
42
|
return {
|
|
38
43
|
a: trace.array ? 1 : 0,
|
|
39
|
-
i: await ctx.parse(
|
|
44
|
+
i: await ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
|
|
40
45
|
};
|
|
41
46
|
},
|
|
42
47
|
stream: parseTrace
|
|
@@ -54,6 +59,18 @@ const ContainerTracePlugin = {
|
|
|
54
59
|
}
|
|
55
60
|
};
|
|
56
61
|
|
|
62
|
+
setContainerTraceStreamMint(iterable => {
|
|
63
|
+
const stream = createStream();
|
|
64
|
+
(async () => {
|
|
65
|
+
try {
|
|
66
|
+
for await (const value of iterable) stream.next(value);
|
|
67
|
+
stream.return(undefined);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
stream.throw(error);
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
return stream;
|
|
73
|
+
});
|
|
57
74
|
const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
|
|
58
75
|
CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
|
|
59
76
|
FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin,
|
|
@@ -8,6 +8,9 @@ const state = globalThis[STATE] || (globalThis[STATE] = {
|
|
|
8
8
|
materialized: new WeakMap(),
|
|
9
9
|
materializedValues: new WeakSet()
|
|
10
10
|
});
|
|
11
|
+
function setContainerTraceStreamMint(fn) {
|
|
12
|
+
state.streamOf = fn;
|
|
13
|
+
}
|
|
11
14
|
const TRACE = Symbol.for("dom-expressions.container-trace");
|
|
12
15
|
function materialize(marker) {
|
|
13
16
|
let value = state.materialized.get(marker.$tr);
|
|
@@ -20,9 +23,10 @@ function materialize(marker) {
|
|
|
20
23
|
}
|
|
21
24
|
function parseTrace(value, ctx) {
|
|
22
25
|
const trace = value[TRACE];
|
|
26
|
+
const sub = trace.subscribe();
|
|
23
27
|
return {
|
|
24
28
|
a: trace.array ? 1 : 0,
|
|
25
|
-
i: ctx.parse(
|
|
29
|
+
i: ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
32
|
const ContainerTracePlugin = {
|
|
@@ -36,9 +40,10 @@ const ContainerTracePlugin = {
|
|
|
36
40
|
},
|
|
37
41
|
async async(value, ctx) {
|
|
38
42
|
const trace = value[TRACE];
|
|
43
|
+
const sub = trace.subscribe();
|
|
39
44
|
return {
|
|
40
45
|
a: trace.array ? 1 : 0,
|
|
41
|
-
i: await ctx.parse(
|
|
46
|
+
i: await ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
|
|
42
47
|
};
|
|
43
48
|
},
|
|
44
49
|
stream: parseTrace
|
|
@@ -56,6 +61,18 @@ const ContainerTracePlugin = {
|
|
|
56
61
|
}
|
|
57
62
|
};
|
|
58
63
|
|
|
64
|
+
setContainerTraceStreamMint(iterable => {
|
|
65
|
+
const stream = seroval.createStream();
|
|
66
|
+
(async () => {
|
|
67
|
+
try {
|
|
68
|
+
for await (const value of iterable) stream.next(value);
|
|
69
|
+
stream.return(undefined);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
stream.throw(error);
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
return stream;
|
|
75
|
+
});
|
|
59
76
|
const DEFAULT_WEB_PLUGINS = Object.freeze([web.AbortSignalPlugin,
|
|
60
77
|
web.CustomEventPlugin, web.DOMExceptionPlugin, web.EventPlugin,
|
|
61
78
|
web.FormDataPlugin, web.HeadersPlugin, web.ReadableStreamPlugin, web.RequestPlugin, web.ResponsePlugin, web.URLSearchParamsPlugin, web.URLPlugin,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Feature, fromCrossJSON, toCrossJSONStream, Serializer, getCrossReferenceHeader } from 'seroval';
|
|
1
|
+
import { Feature, fromCrossJSON, createStream, toCrossJSONStream, Serializer, getCrossReferenceHeader } from 'seroval';
|
|
2
2
|
export { OpaqueReference, createPlugin } from 'seroval';
|
|
3
3
|
import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
|
|
4
4
|
|
|
@@ -7,6 +7,9 @@ const state = globalThis[STATE] || (globalThis[STATE] = {
|
|
|
7
7
|
materialized: new WeakMap(),
|
|
8
8
|
materializedValues: new WeakSet()
|
|
9
9
|
});
|
|
10
|
+
function setContainerTraceStreamMint(fn) {
|
|
11
|
+
state.streamOf = fn;
|
|
12
|
+
}
|
|
10
13
|
const TRACE = Symbol.for("dom-expressions.container-trace");
|
|
11
14
|
function materialize(marker) {
|
|
12
15
|
let value = state.materialized.get(marker.$tr);
|
|
@@ -19,9 +22,10 @@ function materialize(marker) {
|
|
|
19
22
|
}
|
|
20
23
|
function parseTrace(value, ctx) {
|
|
21
24
|
const trace = value[TRACE];
|
|
25
|
+
const sub = trace.subscribe();
|
|
22
26
|
return {
|
|
23
27
|
a: trace.array ? 1 : 0,
|
|
24
|
-
i: ctx.parse(
|
|
28
|
+
i: ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
|
|
25
29
|
};
|
|
26
30
|
}
|
|
27
31
|
const ContainerTracePlugin = {
|
|
@@ -35,9 +39,10 @@ const ContainerTracePlugin = {
|
|
|
35
39
|
},
|
|
36
40
|
async async(value, ctx) {
|
|
37
41
|
const trace = value[TRACE];
|
|
42
|
+
const sub = trace.subscribe();
|
|
38
43
|
return {
|
|
39
44
|
a: trace.array ? 1 : 0,
|
|
40
|
-
i: await ctx.parse(
|
|
45
|
+
i: await ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
|
|
41
46
|
};
|
|
42
47
|
},
|
|
43
48
|
stream: parseTrace
|
|
@@ -55,6 +60,18 @@ const ContainerTracePlugin = {
|
|
|
55
60
|
}
|
|
56
61
|
};
|
|
57
62
|
|
|
63
|
+
setContainerTraceStreamMint(iterable => {
|
|
64
|
+
const stream = createStream();
|
|
65
|
+
(async () => {
|
|
66
|
+
try {
|
|
67
|
+
for await (const value of iterable) stream.next(value);
|
|
68
|
+
stream.return(undefined);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
stream.throw(error);
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
return stream;
|
|
74
|
+
});
|
|
58
75
|
const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
|
|
59
76
|
CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
|
|
60
77
|
FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin,
|
|
@@ -396,6 +396,30 @@ const config = {
|
|
|
396
396
|
responseHandler: undefined,
|
|
397
397
|
serializeArgs: undefined
|
|
398
398
|
};
|
|
399
|
+
const CALL_OBSERVERS = new Set();
|
|
400
|
+
function notifyCallObservers(type, id, instance, value, meta) {
|
|
401
|
+
if (CALL_OBSERVERS.size === 0) return;
|
|
402
|
+
const field = type === "request" ? "request" : "response";
|
|
403
|
+
const time = performance.now();
|
|
404
|
+
for (const observer of new Set(CALL_OBSERVERS)) {
|
|
405
|
+
try {
|
|
406
|
+
observer({
|
|
407
|
+
type,
|
|
408
|
+
id,
|
|
409
|
+
instance,
|
|
410
|
+
[field]: value.clone(),
|
|
411
|
+
meta,
|
|
412
|
+
time
|
|
413
|
+
});
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error(error);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function observeServerFunctionCalls(observer) {
|
|
420
|
+
CALL_OBSERVERS.add(observer);
|
|
421
|
+
return () => CALL_OBSERVERS.delete(observer);
|
|
422
|
+
}
|
|
399
423
|
function serializeArguments(args) {
|
|
400
424
|
if (!config.serializeArgs) {
|
|
401
425
|
throw new Error("Server function arguments are sent as JSON by default and these " + "arguments are not JSON-serializable. Call enableRichArguments() " + '(from "@solidjs/web/server-functions/rich-args") once at startup ' + "to send Dates, Maps, Sets, typed arrays, etc. through the codec — " + "or pass a single Blob/FormData/File argument, which has a native " + "HTTP encoding.");
|
|
@@ -425,6 +449,11 @@ function provideRPC() {
|
|
|
425
449
|
decodeResponse
|
|
426
450
|
});
|
|
427
451
|
}
|
|
452
|
+
function serverFunctionFailure(response, value) {
|
|
453
|
+
const error = value ?? new Error(`Server function call failed with status ${response.status}`);
|
|
454
|
+
if (error instanceof Error && !("status" in error)) error.status = response.status;
|
|
455
|
+
return error;
|
|
456
|
+
}
|
|
428
457
|
async function createRequest(base, id, instance, options, meta) {
|
|
429
458
|
const headers = {
|
|
430
459
|
...options.headers,
|
|
@@ -445,7 +474,12 @@ async function createRequest(base, id, instance, options, meta) {
|
|
|
445
474
|
meta
|
|
446
475
|
})) || init;
|
|
447
476
|
}
|
|
448
|
-
return fetch(base, init);
|
|
477
|
+
if (CALL_OBSERVERS.size === 0) return fetch(base, init);
|
|
478
|
+
const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), init);
|
|
479
|
+
notifyCallObservers("request", id, instance, request, meta);
|
|
480
|
+
const response = await fetch(request);
|
|
481
|
+
notifyCallObservers("response", id, instance, response, meta);
|
|
482
|
+
return response;
|
|
449
483
|
}
|
|
450
484
|
async function initializeResponse(base, id, instance, options, args, meta) {
|
|
451
485
|
if (args.length === 0) {
|
|
@@ -528,6 +562,7 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
528
562
|
});
|
|
529
563
|
if (handled !== undefined) return handled;
|
|
530
564
|
}
|
|
565
|
+
const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
|
|
531
566
|
if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
532
567
|
const consumer = getFlightDataConsumer();
|
|
533
568
|
if (consumer) {
|
|
@@ -535,8 +570,8 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
535
570
|
await consumer(payload.data, {
|
|
536
571
|
response
|
|
537
572
|
});
|
|
538
|
-
if (
|
|
539
|
-
throw payload.value;
|
|
573
|
+
if (failed && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
|
|
574
|
+
throw serverFunctionFailure(response, payload.value);
|
|
540
575
|
}
|
|
541
576
|
return payload.value;
|
|
542
577
|
}
|
|
@@ -545,8 +580,8 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
545
580
|
return response;
|
|
546
581
|
}
|
|
547
582
|
const result = await decodeResponse(response.clone());
|
|
548
|
-
if (
|
|
549
|
-
throw result;
|
|
583
|
+
if (failed) {
|
|
584
|
+
throw serverFunctionFailure(response, result);
|
|
550
585
|
}
|
|
551
586
|
if (controller && result?.[Symbol.asyncIterator]) {
|
|
552
587
|
return {
|
|
@@ -658,10 +693,10 @@ function live(fn) {
|
|
|
658
693
|
iterable.onstatus && iterable.onstatus(state, error);
|
|
659
694
|
} catch {}
|
|
660
695
|
};
|
|
661
|
-
const emitClosed =
|
|
696
|
+
const emitClosed = error => {
|
|
662
697
|
if (ended) return;
|
|
663
698
|
ended = true;
|
|
664
|
-
emit("closed");
|
|
699
|
+
emit("closed", error);
|
|
665
700
|
};
|
|
666
701
|
const closeIt = value => {
|
|
667
702
|
const current = it;
|
|
@@ -713,6 +748,11 @@ function live(fn) {
|
|
|
713
748
|
return r;
|
|
714
749
|
} catch (error) {
|
|
715
750
|
if (!connected) throw error;
|
|
751
|
+
if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
|
|
752
|
+
stopped = true;
|
|
753
|
+
emitClosed(error);
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
716
756
|
it = undefined;
|
|
717
757
|
emit("reconnecting", error);
|
|
718
758
|
await new Promise(resolve => {
|
|
@@ -787,6 +827,7 @@ exports.getServerFunctionsCodec = getServerFunctionsCodec;
|
|
|
787
827
|
exports.hasFlashCookie = hasFlashCookie;
|
|
788
828
|
exports.isServerFunction = isServerFunction;
|
|
789
829
|
exports.live = live;
|
|
830
|
+
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
790
831
|
exports.registerServerReference = registerServerReference;
|
|
791
832
|
exports.serializeString = serializeString;
|
|
792
833
|
exports.subscribeFlightData = subscribeFlightData;
|
|
@@ -394,6 +394,30 @@ const config = {
|
|
|
394
394
|
responseHandler: undefined,
|
|
395
395
|
serializeArgs: undefined
|
|
396
396
|
};
|
|
397
|
+
const CALL_OBSERVERS = new Set();
|
|
398
|
+
function notifyCallObservers(type, id, instance, value, meta) {
|
|
399
|
+
if (CALL_OBSERVERS.size === 0) return;
|
|
400
|
+
const field = type === "request" ? "request" : "response";
|
|
401
|
+
const time = performance.now();
|
|
402
|
+
for (const observer of new Set(CALL_OBSERVERS)) {
|
|
403
|
+
try {
|
|
404
|
+
observer({
|
|
405
|
+
type,
|
|
406
|
+
id,
|
|
407
|
+
instance,
|
|
408
|
+
[field]: value.clone(),
|
|
409
|
+
meta,
|
|
410
|
+
time
|
|
411
|
+
});
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error(error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function observeServerFunctionCalls(observer) {
|
|
418
|
+
CALL_OBSERVERS.add(observer);
|
|
419
|
+
return () => CALL_OBSERVERS.delete(observer);
|
|
420
|
+
}
|
|
397
421
|
function serializeArguments(args) {
|
|
398
422
|
if (!config.serializeArgs) {
|
|
399
423
|
throw new Error("Server function arguments are sent as JSON by default and these " + "arguments are not JSON-serializable. Call enableRichArguments() " + '(from "@solidjs/web/server-functions/rich-args") once at startup ' + "to send Dates, Maps, Sets, typed arrays, etc. through the codec — " + "or pass a single Blob/FormData/File argument, which has a native " + "HTTP encoding.");
|
|
@@ -423,6 +447,11 @@ function provideRPC() {
|
|
|
423
447
|
decodeResponse
|
|
424
448
|
});
|
|
425
449
|
}
|
|
450
|
+
function serverFunctionFailure(response, value) {
|
|
451
|
+
const error = value ?? new Error(`Server function call failed with status ${response.status}`);
|
|
452
|
+
if (error instanceof Error && !("status" in error)) error.status = response.status;
|
|
453
|
+
return error;
|
|
454
|
+
}
|
|
426
455
|
async function createRequest(base, id, instance, options, meta) {
|
|
427
456
|
const headers = {
|
|
428
457
|
...options.headers,
|
|
@@ -443,7 +472,12 @@ async function createRequest(base, id, instance, options, meta) {
|
|
|
443
472
|
meta
|
|
444
473
|
})) || init;
|
|
445
474
|
}
|
|
446
|
-
return fetch(base, init);
|
|
475
|
+
if (CALL_OBSERVERS.size === 0) return fetch(base, init);
|
|
476
|
+
const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), init);
|
|
477
|
+
notifyCallObservers("request", id, instance, request, meta);
|
|
478
|
+
const response = await fetch(request);
|
|
479
|
+
notifyCallObservers("response", id, instance, response, meta);
|
|
480
|
+
return response;
|
|
447
481
|
}
|
|
448
482
|
async function initializeResponse(base, id, instance, options, args, meta) {
|
|
449
483
|
if (args.length === 0) {
|
|
@@ -526,6 +560,7 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
526
560
|
});
|
|
527
561
|
if (handled !== undefined) return handled;
|
|
528
562
|
}
|
|
563
|
+
const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
|
|
529
564
|
if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
530
565
|
const consumer = getFlightDataConsumer();
|
|
531
566
|
if (consumer) {
|
|
@@ -533,8 +568,8 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
533
568
|
await consumer(payload.data, {
|
|
534
569
|
response
|
|
535
570
|
});
|
|
536
|
-
if (
|
|
537
|
-
throw payload.value;
|
|
571
|
+
if (failed && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
|
|
572
|
+
throw serverFunctionFailure(response, payload.value);
|
|
538
573
|
}
|
|
539
574
|
return payload.value;
|
|
540
575
|
}
|
|
@@ -543,8 +578,8 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
543
578
|
return response;
|
|
544
579
|
}
|
|
545
580
|
const result = await decodeResponse(response.clone());
|
|
546
|
-
if (
|
|
547
|
-
throw result;
|
|
581
|
+
if (failed) {
|
|
582
|
+
throw serverFunctionFailure(response, result);
|
|
548
583
|
}
|
|
549
584
|
if (controller && result?.[Symbol.asyncIterator]) {
|
|
550
585
|
return {
|
|
@@ -656,10 +691,10 @@ function live(fn) {
|
|
|
656
691
|
iterable.onstatus && iterable.onstatus(state, error);
|
|
657
692
|
} catch {}
|
|
658
693
|
};
|
|
659
|
-
const emitClosed =
|
|
694
|
+
const emitClosed = error => {
|
|
660
695
|
if (ended) return;
|
|
661
696
|
ended = true;
|
|
662
|
-
emit("closed");
|
|
697
|
+
emit("closed", error);
|
|
663
698
|
};
|
|
664
699
|
const closeIt = value => {
|
|
665
700
|
const current = it;
|
|
@@ -711,6 +746,11 @@ function live(fn) {
|
|
|
711
746
|
return r;
|
|
712
747
|
} catch (error) {
|
|
713
748
|
if (!connected) throw error;
|
|
749
|
+
if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
|
|
750
|
+
stopped = true;
|
|
751
|
+
emitClosed(error);
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
714
754
|
it = undefined;
|
|
715
755
|
emit("reconnecting", error);
|
|
716
756
|
await new Promise(resolve => {
|
|
@@ -760,4 +800,4 @@ function getServerFunctionInvocation() {
|
|
|
760
800
|
return undefined;
|
|
761
801
|
}
|
|
762
802
|
|
|
763
|
-
export { ChunkReader, ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, REVALIDATE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsClient, createChunk, createServerReference, decodeErrorHeaderValue, decodeResponse, decodeResponsePayload, deserializeStream, encodeErrorHeaderValue, frameAddress, getFlightDataConsumer, getServerFunctionInvocation, getServerFunctionMetadata, getServerFunctionsCodec, hasFlashCookie, isServerFunction, live, registerServerReference, serializeString, subscribeFlightData, withMeta };
|
|
803
|
+
export { ChunkReader, ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, REVALIDATE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsClient, createChunk, createServerReference, decodeErrorHeaderValue, decodeResponse, decodeResponsePayload, deserializeStream, encodeErrorHeaderValue, frameAddress, getFlightDataConsumer, getServerFunctionInvocation, getServerFunctionMetadata, getServerFunctionsCodec, hasFlashCookie, isServerFunction, live, observeServerFunctionCalls, registerServerReference, serializeString, subscribeFlightData, withMeta };
|
|
@@ -362,6 +362,7 @@ async function decodeResponsePayload(response, codecOptions) {
|
|
|
362
362
|
};
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
typeof setImmediate === "function" ? setImmediate : fn => setTimeout(fn, 0);
|
|
365
366
|
const RequestContext = Symbol.for("solid.RequestContext");
|
|
366
367
|
function getRequestEvent() {
|
|
367
368
|
return globalThis[RequestContext] ? globalThis[RequestContext].getStore() || solidJs.sharedConfig.context && solidJs.sharedConfig.context.event || console.warn("RequestEvent is missing. This is most likely due to accessing `getRequestEvent` non-managed async scope in a partially polyfilled environment. Try moving it above all `await` calls.") : undefined;
|
|
@@ -493,7 +494,8 @@ const config = {
|
|
|
493
494
|
transformFlightResult: undefined,
|
|
494
495
|
transformDirectResult: undefined,
|
|
495
496
|
handleNoJS: undefined,
|
|
496
|
-
endpoint: "/_server"
|
|
497
|
+
endpoint: "/_server",
|
|
498
|
+
csrf: true
|
|
497
499
|
};
|
|
498
500
|
function configureServerFunctionsServer({
|
|
499
501
|
provideEvent,
|
|
@@ -504,6 +506,7 @@ function configureServerFunctionsServer({
|
|
|
504
506
|
transformDirectResult,
|
|
505
507
|
handleNoJS,
|
|
506
508
|
endpoint,
|
|
509
|
+
csrf,
|
|
507
510
|
codec
|
|
508
511
|
} = {}) {
|
|
509
512
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
@@ -514,6 +517,7 @@ function configureServerFunctionsServer({
|
|
|
514
517
|
if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
|
|
515
518
|
if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
|
|
516
519
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
520
|
+
if (csrf !== undefined) config.csrf = csrf;
|
|
517
521
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
518
522
|
}
|
|
519
523
|
function provideEvent(event, fn) {
|
|
@@ -950,31 +954,96 @@ function sanitizeServerError(value) {
|
|
|
950
954
|
if (isSafeError(value)) return value;
|
|
951
955
|
return new Error(GENERIC_SERVER_ERROR_MESSAGE);
|
|
952
956
|
}
|
|
957
|
+
function observeServerFunctionCalls() {
|
|
958
|
+
return () => {};
|
|
959
|
+
}
|
|
960
|
+
async function matchesOrigin(origin, request, matcher) {
|
|
961
|
+
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
962
|
+
if (typeof matcher === "function") return !!(await matcher(origin, request));
|
|
963
|
+
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
964
|
+
}
|
|
965
|
+
async function allowsServerFunctionRequest(request, options) {
|
|
966
|
+
const fetchSite = request.headers.get("Sec-Fetch-Site");
|
|
967
|
+
if (fetchSite === "same-origin") return true;
|
|
968
|
+
if (fetchSite === "same-site" || fetchSite === "cross-site" || fetchSite === "none") {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
const origin = request.headers.get("Origin");
|
|
972
|
+
if (origin !== null) return matchesOrigin(origin, request, options.origin);
|
|
973
|
+
const referer = request.headers.get("Referer");
|
|
974
|
+
if (referer !== null) {
|
|
975
|
+
try {
|
|
976
|
+
return matchesOrigin(new URL(referer).origin, request, options.origin);
|
|
977
|
+
} catch {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return options.allowRequestsWithoutOriginCheck === true;
|
|
982
|
+
}
|
|
983
|
+
const CSRF_VARY = ["Sec-Fetch-Site", "Origin", "Referer"];
|
|
984
|
+
function withCSRFVary(response) {
|
|
985
|
+
const current = response.headers.get("Vary");
|
|
986
|
+
if (current === "*") return response;
|
|
987
|
+
const values = current ? current.split(",").map(value => value.trim()) : [];
|
|
988
|
+
const names = new Set(values.map(value => value.toLowerCase()));
|
|
989
|
+
for (const value of CSRF_VARY) {
|
|
990
|
+
if (!names.has(value.toLowerCase())) values.push(value);
|
|
991
|
+
}
|
|
992
|
+
const vary = values.join(", ");
|
|
993
|
+
try {
|
|
994
|
+
response.headers.set("Vary", vary);
|
|
995
|
+
return response;
|
|
996
|
+
} catch {
|
|
997
|
+
const headers = new Headers(response.headers);
|
|
998
|
+
headers.set("Vary", vary);
|
|
999
|
+
return new Response(response.body, {
|
|
1000
|
+
status: response.status,
|
|
1001
|
+
statusText: response.statusText,
|
|
1002
|
+
headers
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
function forbiddenResponse() {
|
|
1007
|
+
return withCSRFVary(new Response(DEV ? "Forbidden" : null, {
|
|
1008
|
+
status: 403,
|
|
1009
|
+
headers: {
|
|
1010
|
+
"Cache-Control": "no-store"
|
|
1011
|
+
}
|
|
1012
|
+
}));
|
|
1013
|
+
}
|
|
953
1014
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
954
1015
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
955
1016
|
const url = new URL(request.url);
|
|
1017
|
+
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1018
|
+
const protectsRequest = csrf !== false;
|
|
1019
|
+
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1020
|
+
return forbiddenResponse();
|
|
1021
|
+
}
|
|
956
1022
|
const instance = request.headers.get(INSTANCE_HEADER);
|
|
957
1023
|
const functionId = resolveFunctionId(request, url);
|
|
958
1024
|
if (!functionId) {
|
|
959
|
-
|
|
1025
|
+
const response = new Response(DEV ? "Server function not found" : null, {
|
|
960
1026
|
status: 404
|
|
961
1027
|
});
|
|
1028
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
962
1029
|
}
|
|
963
1030
|
let serverFunction;
|
|
964
1031
|
try {
|
|
965
1032
|
serverFunction = getServerFunction(functionId);
|
|
966
1033
|
} catch {
|
|
967
|
-
|
|
1034
|
+
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
968
1035
|
status: 404
|
|
969
1036
|
});
|
|
1037
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
970
1038
|
}
|
|
971
1039
|
if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
|
|
972
|
-
|
|
1040
|
+
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
973
1041
|
status: 405,
|
|
974
1042
|
headers: {
|
|
975
1043
|
Allow: "POST"
|
|
976
1044
|
}
|
|
977
1045
|
});
|
|
1046
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
978
1047
|
}
|
|
979
1048
|
const event = options.createEvent ? options.createEvent(request) : {
|
|
980
1049
|
request,
|
|
@@ -1133,7 +1202,8 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1133
1202
|
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
1134
1203
|
}
|
|
1135
1204
|
};
|
|
1136
|
-
|
|
1205
|
+
const response = commitEventResponse(await dispatch(), event);
|
|
1206
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
1137
1207
|
}
|
|
1138
1208
|
|
|
1139
1209
|
exports.ERROR_HEADER = ERROR_HEADER;
|
|
@@ -1162,6 +1232,7 @@ exports.handleServerFunctionRequest = handleServerFunctionRequest;
|
|
|
1162
1232
|
exports.hasFlashCookie = hasFlashCookie;
|
|
1163
1233
|
exports.isServerFunction = isServerFunction;
|
|
1164
1234
|
exports.live = live;
|
|
1235
|
+
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
1165
1236
|
exports.registerServerFunction = registerServerFunction;
|
|
1166
1237
|
exports.registerServerReference = registerServerReference;
|
|
1167
1238
|
exports.sanitizeServerError = sanitizeServerError;
|