@solidjs/web 2.0.0-rc.0 → 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/README.md +1 -1
- package/dist/dev.cjs +40 -11
- package/dist/dev.js +39 -12
- package/dist/server.cjs +419 -69
- package/dist/server.js +411 -73
- package/dist/web.cjs +37 -11
- package/dist/web.js +36 -12
- package/frames/dist/client.cjs +94 -8
- package/frames/dist/client.dev.cjs +97 -8
- package/frames/dist/client.dev.js +98 -9
- package/frames/dist/client.js +95 -9
- package/frames/dist/server.cjs +266 -56
- package/frames/dist/server.js +267 -57
- package/package.json +4 -3
- package/serialization/dist/decode.cjs +32 -3
- package/serialization/dist/decode.js +33 -4
- package/serialization/dist/serialization.cjs +32 -3
- package/serialization/dist/serialization.js +33 -4
- package/server-functions/dist/client.cjs +196 -8
- package/server-functions/dist/client.js +195 -9
- package/server-functions/dist/server.cjs +201 -36
- package/server-functions/dist/server.dev.cjs +201 -36
- package/server-functions/dist/server.dev.js +199 -37
- package/server-functions/dist/server.js +199 -37
- package/types/core.d.ts +3 -0
- package/types/frames/frame-client.d.ts +18 -0
- package/types/index.d.ts +16 -2
- package/types/jsx.d.ts +9 -0
- package/types/server-functions/client.d.ts +61 -0
- package/types/server-functions/server.d.ts +94 -1
- 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/index.d.cts +16 -2
- package/types-cjs/jsx.d.cts +9 -0
- package/types-cjs/server-functions/client.d.cts +61 -0
- package/types-cjs/server-functions/server.d.cts +94 -1
- package/types-cjs/server-mock.d.cts +11 -2
- package/types-cjs/server.d.cts +23 -2
|
@@ -16,6 +16,7 @@ function withMeta(fn, meta) {
|
|
|
16
16
|
Object.assign(metadata, meta);
|
|
17
17
|
return fn;
|
|
18
18
|
}
|
|
19
|
+
const LIVE_SOURCE = Symbol.for("solid.LiveSource");
|
|
19
20
|
const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
|
|
20
21
|
function provideServerFunctionRPC(rpc) {
|
|
21
22
|
globalThis[SERVER_FUNCTION_RPC] || (globalThis[SERVER_FUNCTION_RPC] = rpc);
|
|
@@ -136,7 +137,7 @@ const BodyFormat = {
|
|
|
136
137
|
Uint8Array: "7",
|
|
137
138
|
Json: "8"
|
|
138
139
|
};
|
|
139
|
-
const JSON_SAFE_DEPTH_LIMIT =
|
|
140
|
+
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
140
141
|
const EXIT = {};
|
|
141
142
|
function isJSONSafe(value) {
|
|
142
143
|
const stack = [value];
|
|
@@ -163,6 +164,7 @@ function isJSONSafe(value) {
|
|
|
163
164
|
} else {
|
|
164
165
|
const proto = Object.getPrototypeOf(v);
|
|
165
166
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
167
|
+
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
166
168
|
for (const k in v) stack.push(v[k]);
|
|
167
169
|
}
|
|
168
170
|
}
|
|
@@ -364,7 +366,7 @@ async function deserializeStream(source, codecOptions) {
|
|
|
364
366
|
function interpretChunk(chunk) {
|
|
365
367
|
return deserializeChunk(JSON.parse(chunk));
|
|
366
368
|
}
|
|
367
|
-
|
|
369
|
+
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
368
370
|
return interpretChunk(result.value);
|
|
369
371
|
}
|
|
370
372
|
return undefined;
|
|
@@ -392,6 +394,30 @@ const config = {
|
|
|
392
394
|
responseHandler: undefined,
|
|
393
395
|
serializeArgs: undefined
|
|
394
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
|
+
}
|
|
395
421
|
function serializeArguments(args) {
|
|
396
422
|
if (!config.serializeArgs) {
|
|
397
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.");
|
|
@@ -421,13 +447,18 @@ function provideRPC() {
|
|
|
421
447
|
decodeResponse
|
|
422
448
|
});
|
|
423
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
|
+
}
|
|
424
455
|
async function createRequest(base, id, instance, options, meta) {
|
|
425
456
|
const headers = {
|
|
426
457
|
...options.headers,
|
|
427
458
|
[FUNCTION_HEADER]: id,
|
|
428
459
|
[INSTANCE_HEADER]: instance
|
|
429
460
|
};
|
|
430
|
-
if (getFlightDataConsumer() && (!options.method || options.method.toUpperCase() !== "GET")) {
|
|
461
|
+
if (getFlightDataConsumer() && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
|
|
431
462
|
headers[SINGLE_FLIGHT_HEADER] = "true";
|
|
432
463
|
}
|
|
433
464
|
let init = {
|
|
@@ -441,7 +472,12 @@ async function createRequest(base, id, instance, options, meta) {
|
|
|
441
472
|
meta
|
|
442
473
|
})) || init;
|
|
443
474
|
}
|
|
444
|
-
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;
|
|
445
481
|
}
|
|
446
482
|
async function initializeResponse(base, id, instance, options, args, meta) {
|
|
447
483
|
if (args.length === 0) {
|
|
@@ -509,6 +545,11 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
509
545
|
id,
|
|
510
546
|
meta
|
|
511
547
|
}) : undefined;
|
|
548
|
+
const controller = options.signal ? undefined : new AbortController();
|
|
549
|
+
if (controller) options = {
|
|
550
|
+
...options,
|
|
551
|
+
signal: controller.signal
|
|
552
|
+
};
|
|
512
553
|
const response = await initializeResponse(base, id, instance, options, args, meta);
|
|
513
554
|
if (handler) {
|
|
514
555
|
const handled = handler.handle(response, {
|
|
@@ -519,6 +560,7 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
519
560
|
});
|
|
520
561
|
if (handled !== undefined) return handled;
|
|
521
562
|
}
|
|
563
|
+
const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
|
|
522
564
|
if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
523
565
|
const consumer = getFlightDataConsumer();
|
|
524
566
|
if (consumer) {
|
|
@@ -526,8 +568,8 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
526
568
|
await consumer(payload.data, {
|
|
527
569
|
response
|
|
528
570
|
});
|
|
529
|
-
if (
|
|
530
|
-
throw payload.value;
|
|
571
|
+
if (failed && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
|
|
572
|
+
throw serverFunctionFailure(response, payload.value);
|
|
531
573
|
}
|
|
532
574
|
return payload.value;
|
|
533
575
|
}
|
|
@@ -536,8 +578,22 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
536
578
|
return response;
|
|
537
579
|
}
|
|
538
580
|
const result = await decodeResponse(response.clone());
|
|
539
|
-
if (
|
|
540
|
-
throw result;
|
|
581
|
+
if (failed) {
|
|
582
|
+
throw serverFunctionFailure(response, result);
|
|
583
|
+
}
|
|
584
|
+
if (controller && result?.[Symbol.asyncIterator]) {
|
|
585
|
+
return {
|
|
586
|
+
[Symbol.asyncIterator]() {
|
|
587
|
+
const it = result[Symbol.asyncIterator]();
|
|
588
|
+
return {
|
|
589
|
+
next: () => it.next(),
|
|
590
|
+
return: value => (controller.abort(), Promise.resolve({
|
|
591
|
+
done: true,
|
|
592
|
+
value
|
|
593
|
+
}))
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
};
|
|
541
597
|
}
|
|
542
598
|
return result;
|
|
543
599
|
}
|
|
@@ -607,6 +663,136 @@ function GET(fn) {
|
|
|
607
663
|
method: "GET"
|
|
608
664
|
});
|
|
609
665
|
}
|
|
666
|
+
function live(fn) {
|
|
667
|
+
if (!isServerFunction(fn)) {
|
|
668
|
+
throw new Error("live expects a server function reference");
|
|
669
|
+
}
|
|
670
|
+
const id = fn.id;
|
|
671
|
+
const metadata = {
|
|
672
|
+
...getServerFunctionMetadata(fn),
|
|
673
|
+
live: true
|
|
674
|
+
};
|
|
675
|
+
const wrapped = (...args) => {
|
|
676
|
+
const iterable = {
|
|
677
|
+
[LIVE_SOURCE]: true,
|
|
678
|
+
[Symbol.asyncIterator]() {
|
|
679
|
+
let it;
|
|
680
|
+
let connected = false;
|
|
681
|
+
let attempts = 0;
|
|
682
|
+
let stopped = false;
|
|
683
|
+
let ended = false;
|
|
684
|
+
let timer, wake;
|
|
685
|
+
const DONE = {
|
|
686
|
+
done: true,
|
|
687
|
+
value: undefined
|
|
688
|
+
};
|
|
689
|
+
const emit = (state, error) => {
|
|
690
|
+
try {
|
|
691
|
+
iterable.onstatus && iterable.onstatus(state, error);
|
|
692
|
+
} catch {}
|
|
693
|
+
};
|
|
694
|
+
const emitClosed = error => {
|
|
695
|
+
if (ended) return;
|
|
696
|
+
ended = true;
|
|
697
|
+
emit("closed", error);
|
|
698
|
+
};
|
|
699
|
+
const closeIt = value => {
|
|
700
|
+
const current = it;
|
|
701
|
+
it = undefined;
|
|
702
|
+
if (current) {
|
|
703
|
+
try {
|
|
704
|
+
const r = current.return && current.return(value);
|
|
705
|
+
if (r && typeof r.then === "function") r.then(undefined, () => {});
|
|
706
|
+
} catch {}
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const callOnce = () => {
|
|
710
|
+
if (metadata.method === "GET") return fn(...args);
|
|
711
|
+
const handler = config.responseHandler;
|
|
712
|
+
if (handler && handler.intercept) {
|
|
713
|
+
const hit = handler.intercept({
|
|
714
|
+
id,
|
|
715
|
+
meta: metadata,
|
|
716
|
+
args
|
|
717
|
+
});
|
|
718
|
+
if (hit !== undefined) return hit;
|
|
719
|
+
}
|
|
720
|
+
return fetchServerFunction(fn.url, id, {
|
|
721
|
+
read: true
|
|
722
|
+
}, args, metadata, args);
|
|
723
|
+
};
|
|
724
|
+
const pull = async () => {
|
|
725
|
+
while (!stopped) {
|
|
726
|
+
try {
|
|
727
|
+
if (!it) {
|
|
728
|
+
const result = await callOnce();
|
|
729
|
+
connected = true;
|
|
730
|
+
it = result !== null && typeof result === "object" && result[Symbol.asyncIterator] ? result[Symbol.asyncIterator]() : async function* () {
|
|
731
|
+
yield result;
|
|
732
|
+
}();
|
|
733
|
+
if (stopped) {
|
|
734
|
+
closeIt();
|
|
735
|
+
return DONE;
|
|
736
|
+
}
|
|
737
|
+
emit("connected");
|
|
738
|
+
}
|
|
739
|
+
const r = await it.next();
|
|
740
|
+
if (r.done) {
|
|
741
|
+
emitClosed();
|
|
742
|
+
return DONE;
|
|
743
|
+
}
|
|
744
|
+
if (stopped) return DONE;
|
|
745
|
+
attempts = 0;
|
|
746
|
+
return r;
|
|
747
|
+
} catch (error) {
|
|
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
|
+
}
|
|
754
|
+
it = undefined;
|
|
755
|
+
emit("reconnecting", error);
|
|
756
|
+
await new Promise(resolve => {
|
|
757
|
+
wake = resolve;
|
|
758
|
+
timer = setTimeout(resolve, Math.min(500 * 2 ** attempts++, 10000));
|
|
759
|
+
if (typeof addEventListener === "function") addEventListener("online", resolve, {
|
|
760
|
+
once: true
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
clearTimeout(timer);
|
|
764
|
+
if (typeof removeEventListener === "function") removeEventListener("online", wake);
|
|
765
|
+
timer = wake = undefined;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return DONE;
|
|
769
|
+
};
|
|
770
|
+
return {
|
|
771
|
+
next: () => pull(),
|
|
772
|
+
return(value) {
|
|
773
|
+
stopped = true;
|
|
774
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
775
|
+
if (wake) wake();
|
|
776
|
+
closeIt(value);
|
|
777
|
+
emitClosed();
|
|
778
|
+
return Promise.resolve({
|
|
779
|
+
done: true,
|
|
780
|
+
value
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
return iterable;
|
|
787
|
+
};
|
|
788
|
+
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
789
|
+
wrapped.id = id;
|
|
790
|
+
Object.defineProperty(wrapped, "url", {
|
|
791
|
+
get: () => fn.url,
|
|
792
|
+
configurable: true
|
|
793
|
+
});
|
|
794
|
+
return wrapped;
|
|
795
|
+
}
|
|
610
796
|
function registerServerReference() {
|
|
611
797
|
throw new Error("registerServerReference must not be called in the client build");
|
|
612
798
|
}
|
|
@@ -614,4 +800,4 @@ function getServerFunctionInvocation() {
|
|
|
614
800
|
return undefined;
|
|
615
801
|
}
|
|
616
802
|
|
|
617
|
-
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, 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 };
|
|
@@ -28,6 +28,7 @@ function withMeta(fn, meta) {
|
|
|
28
28
|
Object.assign(metadata, meta);
|
|
29
29
|
return fn;
|
|
30
30
|
}
|
|
31
|
+
const LIVE_SOURCE = Symbol.for("solid.LiveSource");
|
|
31
32
|
const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
|
|
32
33
|
function provideServerFunctionRPC(rpc) {
|
|
33
34
|
globalThis[SERVER_FUNCTION_RPC] || (globalThis[SERVER_FUNCTION_RPC] = rpc);
|
|
@@ -132,7 +133,7 @@ const BodyFormat = {
|
|
|
132
133
|
Uint8Array: "7",
|
|
133
134
|
Json: "8"
|
|
134
135
|
};
|
|
135
|
-
const JSON_SAFE_DEPTH_LIMIT =
|
|
136
|
+
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
136
137
|
const EXIT = {};
|
|
137
138
|
function isJSONSafe(value) {
|
|
138
139
|
const stack = [value];
|
|
@@ -159,6 +160,7 @@ function isJSONSafe(value) {
|
|
|
159
160
|
} else {
|
|
160
161
|
const proto = Object.getPrototypeOf(v);
|
|
161
162
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
163
|
+
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
162
164
|
for (const k in v) stack.push(v[k]);
|
|
163
165
|
}
|
|
164
166
|
}
|
|
@@ -321,27 +323,6 @@ class ChunkReader {
|
|
|
321
323
|
}
|
|
322
324
|
}
|
|
323
325
|
}
|
|
324
|
-
function serializeStream(value, codecOptions) {
|
|
325
|
-
return new ReadableStream({
|
|
326
|
-
async start(controller) {
|
|
327
|
-
const {
|
|
328
|
-
serializeJSON
|
|
329
|
-
} = await import('@solidjs/web/serialization');
|
|
330
|
-
serializeJSON(value, {
|
|
331
|
-
...codecOptions,
|
|
332
|
-
onParse(node) {
|
|
333
|
-
controller.enqueue(createChunk(JSON.stringify(node)));
|
|
334
|
-
},
|
|
335
|
-
onDone() {
|
|
336
|
-
controller.close();
|
|
337
|
-
},
|
|
338
|
-
onError(error) {
|
|
339
|
-
controller.error(error);
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
326
|
async function deserializeStream(source, codecOptions) {
|
|
346
327
|
if (!source.body) {
|
|
347
328
|
throw new Error("missing body");
|
|
@@ -356,7 +337,7 @@ async function deserializeStream(source, codecOptions) {
|
|
|
356
337
|
function interpretChunk(chunk) {
|
|
357
338
|
return deserializeChunk(JSON.parse(chunk));
|
|
358
339
|
}
|
|
359
|
-
|
|
340
|
+
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
360
341
|
return interpretChunk(result.value);
|
|
361
342
|
}
|
|
362
343
|
return undefined;
|
|
@@ -381,6 +362,7 @@ async function decodeResponsePayload(response, codecOptions) {
|
|
|
381
362
|
};
|
|
382
363
|
}
|
|
383
364
|
|
|
365
|
+
typeof setImmediate === "function" ? setImmediate : fn => setTimeout(fn, 0);
|
|
384
366
|
const RequestContext = Symbol.for("solid.RequestContext");
|
|
385
367
|
function getRequestEvent() {
|
|
386
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;
|
|
@@ -512,7 +494,8 @@ const config = {
|
|
|
512
494
|
transformFlightResult: undefined,
|
|
513
495
|
transformDirectResult: undefined,
|
|
514
496
|
handleNoJS: undefined,
|
|
515
|
-
endpoint: "/_server"
|
|
497
|
+
endpoint: "/_server",
|
|
498
|
+
csrf: true
|
|
516
499
|
};
|
|
517
500
|
function configureServerFunctionsServer({
|
|
518
501
|
provideEvent,
|
|
@@ -523,6 +506,7 @@ function configureServerFunctionsServer({
|
|
|
523
506
|
transformDirectResult,
|
|
524
507
|
handleNoJS,
|
|
525
508
|
endpoint,
|
|
509
|
+
csrf,
|
|
526
510
|
codec
|
|
527
511
|
} = {}) {
|
|
528
512
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
@@ -533,6 +517,7 @@ function configureServerFunctionsServer({
|
|
|
533
517
|
if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
|
|
534
518
|
if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
|
|
535
519
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
520
|
+
if (csrf !== undefined) config.csrf = csrf;
|
|
536
521
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
537
522
|
}
|
|
538
523
|
function provideEvent(event, fn) {
|
|
@@ -636,6 +621,29 @@ function GET(fn) {
|
|
|
636
621
|
method: "GET"
|
|
637
622
|
});
|
|
638
623
|
}
|
|
624
|
+
function live(fn) {
|
|
625
|
+
if (!isServerFunction(fn) || typeof fn.id !== "string") {
|
|
626
|
+
throw new Error("live expects a server function reference");
|
|
627
|
+
}
|
|
628
|
+
const metadata = {
|
|
629
|
+
...getServerFunctionMetadata(fn),
|
|
630
|
+
live: true
|
|
631
|
+
};
|
|
632
|
+
const wrapped = async (...args) => {
|
|
633
|
+
const result = await fn(...args);
|
|
634
|
+
if (result !== null && typeof result === "object" && result[Symbol.asyncIterator]) {
|
|
635
|
+
result[LIVE_SOURCE] = true;
|
|
636
|
+
}
|
|
637
|
+
return result;
|
|
638
|
+
};
|
|
639
|
+
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
640
|
+
wrapped.id = fn.id;
|
|
641
|
+
Object.defineProperty(wrapped, "url", {
|
|
642
|
+
get: () => fn.url,
|
|
643
|
+
configurable: true
|
|
644
|
+
});
|
|
645
|
+
return wrapped;
|
|
646
|
+
}
|
|
639
647
|
function getServerFunctionInvocation() {
|
|
640
648
|
return getEventServerFunctionInvocation(getRequestEvent());
|
|
641
649
|
}
|
|
@@ -807,14 +815,102 @@ function isFormPost(request) {
|
|
|
807
815
|
const type = request.headers.get("content-type") || "";
|
|
808
816
|
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
809
817
|
}
|
|
810
|
-
function
|
|
818
|
+
function serializeResponseStream(value, codecOptions, signal) {
|
|
819
|
+
let closeIterator = null;
|
|
820
|
+
let closed = false;
|
|
821
|
+
let cancelSerialize = null;
|
|
822
|
+
let onAbort = null;
|
|
823
|
+
const teardown = () => {
|
|
824
|
+
if (closed) return;
|
|
825
|
+
closed = true;
|
|
826
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
827
|
+
if (cancelSerialize) cancelSerialize();
|
|
828
|
+
if (closeIterator) closeIterator();
|
|
829
|
+
};
|
|
830
|
+
if (value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function") {
|
|
831
|
+
const source = value;
|
|
832
|
+
value = {
|
|
833
|
+
[Symbol.asyncIterator]() {
|
|
834
|
+
const it = source[Symbol.asyncIterator]();
|
|
835
|
+
let finished = false;
|
|
836
|
+
closeIterator = () => {
|
|
837
|
+
if (finished) return;
|
|
838
|
+
finished = true;
|
|
839
|
+
try {
|
|
840
|
+
const returned = it.return && it.return();
|
|
841
|
+
if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
|
|
842
|
+
} catch {}
|
|
843
|
+
};
|
|
844
|
+
if (closed) closeIterator();
|
|
845
|
+
return {
|
|
846
|
+
next: () => finished ? Promise.resolve({
|
|
847
|
+
done: true,
|
|
848
|
+
value: undefined
|
|
849
|
+
}) : it.next()
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
return new ReadableStream({
|
|
855
|
+
async start(controller) {
|
|
856
|
+
if (signal) {
|
|
857
|
+
if (signal.aborted) {
|
|
858
|
+
teardown();
|
|
859
|
+
controller.close();
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
onAbort = () => {
|
|
863
|
+
const alreadyClosed = closed;
|
|
864
|
+
teardown();
|
|
865
|
+
if (!alreadyClosed) {
|
|
866
|
+
try {
|
|
867
|
+
controller.error(signal.reason || new Error("The operation was aborted."));
|
|
868
|
+
} catch {}
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
signal.addEventListener("abort", onAbort);
|
|
872
|
+
}
|
|
873
|
+
const {
|
|
874
|
+
serializeJSON
|
|
875
|
+
} = await import('@solidjs/web/serialization');
|
|
876
|
+
if (closed) {
|
|
877
|
+
try {
|
|
878
|
+
controller.close();
|
|
879
|
+
} catch {}
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
cancelSerialize = serializeJSON(value, {
|
|
883
|
+
...codecOptions,
|
|
884
|
+
onParse(node) {
|
|
885
|
+
if (!closed) controller.enqueue(createChunk(JSON.stringify(node)));
|
|
886
|
+
},
|
|
887
|
+
onDone() {
|
|
888
|
+
if (closed) return;
|
|
889
|
+
closed = true;
|
|
890
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
891
|
+
controller.close();
|
|
892
|
+
},
|
|
893
|
+
onError(error) {
|
|
894
|
+
if (closed) return;
|
|
895
|
+
closed = true;
|
|
896
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
897
|
+
controller.error(error);
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
},
|
|
901
|
+
cancel() {
|
|
902
|
+
teardown();
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
function serializedResponse(value, headers, codec, signal) {
|
|
811
907
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
|
|
812
908
|
headers.set("Content-Type", "text/plain");
|
|
813
|
-
return new Response(
|
|
909
|
+
return new Response(serializeResponseStream(value, codec, signal), {
|
|
814
910
|
headers
|
|
815
911
|
});
|
|
816
912
|
}
|
|
817
|
-
function encodeResult(value, headers, status, codec) {
|
|
913
|
+
function encodeResult(value, headers, status, codec, signal) {
|
|
818
914
|
const direct = getHeadersAndBody(value);
|
|
819
915
|
if (direct) {
|
|
820
916
|
for (const [key, val] of Object.entries(direct.headers || {})) {
|
|
@@ -842,7 +938,7 @@ function encodeResult(value, headers, status, codec) {
|
|
|
842
938
|
}
|
|
843
939
|
} catch {
|
|
844
940
|
}
|
|
845
|
-
const response = serializedResponse(value, headers, codec);
|
|
941
|
+
const response = serializedResponse(value, headers, codec, signal);
|
|
846
942
|
return status === 200 ? response : new Response(response.body, {
|
|
847
943
|
status,
|
|
848
944
|
headers
|
|
@@ -858,31 +954,96 @@ function sanitizeServerError(value) {
|
|
|
858
954
|
if (isSafeError(value)) return value;
|
|
859
955
|
return new Error(GENERIC_SERVER_ERROR_MESSAGE);
|
|
860
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
|
+
}
|
|
861
1014
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
862
1015
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
863
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
|
+
}
|
|
864
1022
|
const instance = request.headers.get(INSTANCE_HEADER);
|
|
865
1023
|
const functionId = resolveFunctionId(request, url);
|
|
866
1024
|
if (!functionId) {
|
|
867
|
-
|
|
1025
|
+
const response = new Response(DEV ? "Server function not found" : null, {
|
|
868
1026
|
status: 404
|
|
869
1027
|
});
|
|
1028
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
870
1029
|
}
|
|
871
1030
|
let serverFunction;
|
|
872
1031
|
try {
|
|
873
1032
|
serverFunction = getServerFunction(functionId);
|
|
874
1033
|
} catch {
|
|
875
|
-
|
|
1034
|
+
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
876
1035
|
status: 404
|
|
877
1036
|
});
|
|
1037
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
878
1038
|
}
|
|
879
1039
|
if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
|
|
880
|
-
|
|
1040
|
+
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
881
1041
|
status: 405,
|
|
882
1042
|
headers: {
|
|
883
1043
|
Allow: "POST"
|
|
884
1044
|
}
|
|
885
1045
|
});
|
|
1046
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
886
1047
|
}
|
|
887
1048
|
const event = options.createEvent ? options.createEvent(request) : {
|
|
888
1049
|
request,
|
|
@@ -970,9 +1131,9 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
970
1131
|
if (!instance) {
|
|
971
1132
|
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
972
1133
|
if (result instanceof Response) return result;
|
|
973
|
-
return encodeResult(result, headers, 200, codec);
|
|
1134
|
+
return encodeResult(result, headers, 200, codec, request.signal);
|
|
974
1135
|
}
|
|
975
|
-
return encodeResult(result, headers, status, codec);
|
|
1136
|
+
return encodeResult(result, headers, status, codec, request.signal);
|
|
976
1137
|
} catch (x) {
|
|
977
1138
|
if (x instanceof Response || isResponseEnvelope(x)) {
|
|
978
1139
|
if (transformResult) {
|
|
@@ -1026,7 +1187,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1026
1187
|
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
1027
1188
|
if (x instanceof Response) return x;
|
|
1028
1189
|
}
|
|
1029
|
-
return encodeResult(x, headers, status, codec);
|
|
1190
|
+
return encodeResult(x, headers, status, codec, request.signal);
|
|
1030
1191
|
}
|
|
1031
1192
|
const safe = sanitizeServerError(x);
|
|
1032
1193
|
if (!instance) {
|
|
@@ -1038,10 +1199,11 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1038
1199
|
}
|
|
1039
1200
|
const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
|
|
1040
1201
|
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
|
|
1041
|
-
return encodeResult(safe, headers, 200, codec);
|
|
1202
|
+
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
1042
1203
|
}
|
|
1043
1204
|
};
|
|
1044
|
-
|
|
1205
|
+
const response = commitEventResponse(await dispatch(), event);
|
|
1206
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
1045
1207
|
}
|
|
1046
1208
|
|
|
1047
1209
|
exports.ERROR_HEADER = ERROR_HEADER;
|
|
@@ -1069,9 +1231,12 @@ exports.getServerFunctionMetadata = getServerFunctionMetadata;
|
|
|
1069
1231
|
exports.handleServerFunctionRequest = handleServerFunctionRequest;
|
|
1070
1232
|
exports.hasFlashCookie = hasFlashCookie;
|
|
1071
1233
|
exports.isServerFunction = isServerFunction;
|
|
1234
|
+
exports.live = live;
|
|
1235
|
+
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
1072
1236
|
exports.registerServerFunction = registerServerFunction;
|
|
1073
1237
|
exports.registerServerReference = registerServerReference;
|
|
1074
1238
|
exports.sanitizeServerError = sanitizeServerError;
|
|
1239
|
+
exports.serializeResponseStream = serializeResponseStream;
|
|
1075
1240
|
exports.setServerFunctionsDev = setServerFunctionsDev;
|
|
1076
1241
|
exports.subscribeFlightData = subscribeFlightData;
|
|
1077
1242
|
exports.withMeta = withMeta;
|