@solidjs/web 2.0.0-rc.4 → 2.0.0-rc.5
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 +52 -7
- package/dist/dev.js +51 -8
- package/dist/server.cjs +162 -35
- package/dist/server.js +161 -36
- package/dist/web.cjs +32 -7
- package/dist/web.js +31 -8
- package/frames/dist/client.cjs +41 -8
- package/frames/dist/client.dev.cjs +41 -8
- package/frames/dist/client.dev.js +41 -8
- package/frames/dist/client.js +41 -8
- package/frames/dist/server.cjs +432 -44
- package/frames/dist/server.js +432 -44
- package/package.json +2 -2
- package/serialization/dist/decode.cjs +4 -2
- package/serialization/dist/decode.js +4 -2
- package/serialization/dist/serialization.cjs +12 -8
- package/serialization/dist/serialization.js +12 -8
- package/serialization/types/index.d.ts +7 -0
- package/serialization/types/serializer-decode.d.ts +14 -1
- package/serialization/types/serializer.d.ts +7 -0
- package/serialization/types-cjs/index.d.cts +7 -0
- package/serialization/types-cjs/serializer-decode.d.cts +14 -1
- package/serialization/types-cjs/serializer.d.cts +7 -0
- package/server-functions/dist/client.cjs +150 -33
- package/server-functions/dist/client.js +147 -34
- package/server-functions/dist/server.cjs +731 -118
- package/server-functions/dist/server.dev.cjs +751 -118
- package/server-functions/dist/server.dev.js +747 -119
- package/server-functions/dist/server.js +727 -119
- package/types/cookies.d.ts +6 -14
- package/types/frames/frame-client.d.ts +4 -0
- package/types/frames/serializer-decode.d.ts +14 -1
- package/types/frames/serializer.d.ts +7 -0
- package/types/jsx.d.ts +11 -16
- package/types/response.d.ts +11 -0
- package/types/serializer-decode.d.ts +14 -1
- package/types/serializer.d.ts +7 -0
- package/types/server-functions/client.d.ts +22 -2
- package/types/server-functions/flash.d.ts +9 -0
- package/types/server-functions/server.d.ts +58 -9
- package/types/server-functions/shared.d.ts +77 -14
- package/types/server-mock.d.ts +17 -2
- package/types/server.d.ts +16 -2
- package/types-cjs/cookies.d.cts +6 -14
- package/types-cjs/frames/frame-client.d.cts +4 -0
- package/types-cjs/frames/serializer-decode.d.cts +14 -1
- package/types-cjs/frames/serializer.d.cts +7 -0
- package/types-cjs/jsx.d.cts +11 -16
- package/types-cjs/response.d.cts +11 -0
- package/types-cjs/serializer-decode.d.cts +14 -1
- package/types-cjs/serializer.d.cts +7 -0
- package/types-cjs/server-functions/client.d.cts +22 -2
- package/types-cjs/server-functions/flash.d.cts +9 -0
- package/types-cjs/server-functions/server.d.cts +58 -9
- package/types-cjs/server-functions/shared.d.cts +77 -14
- package/types-cjs/server-mock.d.cts +17 -2
- package/types-cjs/server.d.cts +16 -2
|
@@ -66,17 +66,30 @@ function configureServerFunctionsCodec(codec) {
|
|
|
66
66
|
function getServerFunctionsCodec() {
|
|
67
67
|
return codecConfig.codec;
|
|
68
68
|
}
|
|
69
|
+
const UNNAMED_FLIGHT_SOURCE = "true";
|
|
69
70
|
const flightConfig = {
|
|
70
|
-
|
|
71
|
+
consumers: new Map()
|
|
71
72
|
};
|
|
72
|
-
function
|
|
73
|
-
|
|
73
|
+
function assertFlightSource(source) {
|
|
74
|
+
if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
|
|
75
|
+
throw new TypeError(`Invalid flight data source id "${source}": ids ride the ` + `${SINGLE_FLIGHT_HEADER} header as a comma-separated list, and "true" is ` + `reserved for the unnamed registration (the bare consumer/hook signatures).`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
|
|
79
|
+
const named = typeof sourceOrConsumer === "string";
|
|
80
|
+
if (named) assertFlightSource(sourceOrConsumer);
|
|
81
|
+
const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
|
|
82
|
+
const consumer = named ? maybeConsumer : sourceOrConsumer;
|
|
83
|
+
flightConfig.consumers.set(source, consumer);
|
|
74
84
|
return () => {
|
|
75
|
-
if (flightConfig.
|
|
85
|
+
if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
|
|
76
86
|
};
|
|
77
87
|
}
|
|
78
|
-
function getFlightDataConsumer() {
|
|
79
|
-
return flightConfig.
|
|
88
|
+
function getFlightDataConsumer(source) {
|
|
89
|
+
return flightConfig.consumers.get(source === undefined ? UNNAMED_FLIGHT_SOURCE : source);
|
|
90
|
+
}
|
|
91
|
+
function getFlightDataSourceIds() {
|
|
92
|
+
return [...flightConfig.consumers.keys()];
|
|
80
93
|
}
|
|
81
94
|
function frameAddress(id, args) {
|
|
82
95
|
return args && args.length ? id + ":" + hashArguments(args) : id;
|
|
@@ -126,15 +139,27 @@ function serverFunctionAddress(endpoint, id) {
|
|
|
126
139
|
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
127
140
|
return `${mount}/${encodeURIComponent(id)}`;
|
|
128
141
|
}
|
|
142
|
+
function serverFunctionDataAddress(endpoint, id) {
|
|
143
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
144
|
+
return `${mount}/data/${encodeURIComponent(id)}`;
|
|
145
|
+
}
|
|
129
146
|
function parseServerFunctionAddress(pathname, endpoint) {
|
|
130
147
|
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
131
148
|
if (!pathname.startsWith(mount)) return null;
|
|
132
149
|
const rest = pathname.slice(mount.length);
|
|
133
150
|
if (!rest.startsWith("/")) return null;
|
|
134
|
-
|
|
151
|
+
let segment = rest.slice(1);
|
|
152
|
+
let data = false;
|
|
153
|
+
if (segment.startsWith("data/")) {
|
|
154
|
+
segment = segment.slice(5);
|
|
155
|
+
data = true;
|
|
156
|
+
}
|
|
135
157
|
if (!segment || segment.includes("/")) return null;
|
|
136
158
|
try {
|
|
137
|
-
return
|
|
159
|
+
return {
|
|
160
|
+
id: decodeURIComponent(segment),
|
|
161
|
+
data
|
|
162
|
+
};
|
|
138
163
|
} catch {
|
|
139
164
|
return null;
|
|
140
165
|
}
|
|
@@ -166,6 +191,28 @@ function decodeErrorHeaderValue(value) {
|
|
|
166
191
|
}
|
|
167
192
|
const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
168
193
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
194
|
+
const UNKNOWN_HEADER = "X-Server-Function-Unknown";
|
|
195
|
+
const REDIRECT_HEADER = "X-Server-Function-Redirect";
|
|
196
|
+
function decodeRedirectHeaderValue(value) {
|
|
197
|
+
if (typeof value !== "string") return undefined;
|
|
198
|
+
const at = value.indexOf(" ");
|
|
199
|
+
if (at < 0) return undefined;
|
|
200
|
+
const status = Number(value.slice(0, at));
|
|
201
|
+
const url = value.slice(at + 1);
|
|
202
|
+
if (!Number.isInteger(status) || !url) return undefined;
|
|
203
|
+
if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
|
|
204
|
+
let parsed;
|
|
205
|
+
try {
|
|
206
|
+
parsed = new URL(url);
|
|
207
|
+
} catch {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
|
|
211
|
+
return {
|
|
212
|
+
status,
|
|
213
|
+
url
|
|
214
|
+
};
|
|
215
|
+
}
|
|
169
216
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
170
217
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
171
218
|
const BodyFormat = {
|
|
@@ -208,7 +255,11 @@ function isJSONSafe(value) {
|
|
|
208
255
|
const proto = Object.getPrototypeOf(v);
|
|
209
256
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
210
257
|
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
211
|
-
for (const k in v)
|
|
258
|
+
for (const k in v) {
|
|
259
|
+
const descriptor = Object.getOwnPropertyDescriptor(v, k);
|
|
260
|
+
if (descriptor === undefined || !("value" in descriptor)) return false;
|
|
261
|
+
stack.push(descriptor.value);
|
|
262
|
+
}
|
|
212
263
|
}
|
|
213
264
|
}
|
|
214
265
|
return true;
|
|
@@ -317,18 +368,34 @@ function createChunk(data) {
|
|
|
317
368
|
class ChunkReader {
|
|
318
369
|
constructor(stream) {
|
|
319
370
|
this.reader = stream.getReader();
|
|
320
|
-
this.
|
|
371
|
+
this.store = new Uint8Array(0);
|
|
372
|
+
this.buffer = this.store;
|
|
321
373
|
this.done = false;
|
|
322
374
|
}
|
|
323
375
|
async readChunk() {
|
|
324
376
|
const chunk = await this.reader.read();
|
|
325
|
-
if (
|
|
326
|
-
const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
|
|
327
|
-
newBuffer.set(this.buffer);
|
|
328
|
-
newBuffer.set(chunk.value, this.buffer.length);
|
|
329
|
-
this.buffer = newBuffer;
|
|
330
|
-
} else {
|
|
377
|
+
if (chunk.done) {
|
|
331
378
|
this.done = true;
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const incoming = chunk.value;
|
|
382
|
+
const store = this.store;
|
|
383
|
+
const start = this.buffer.byteOffset;
|
|
384
|
+
const end = start + this.buffer.length;
|
|
385
|
+
const needed = this.buffer.length + incoming.length;
|
|
386
|
+
if (end + incoming.length <= store.length) {
|
|
387
|
+
store.set(incoming, end);
|
|
388
|
+
this.buffer = store.subarray(start, end + incoming.length);
|
|
389
|
+
} else if (needed <= store.length) {
|
|
390
|
+
store.copyWithin(0, start, end);
|
|
391
|
+
store.set(incoming, this.buffer.length);
|
|
392
|
+
this.buffer = store.subarray(0, needed);
|
|
393
|
+
} else {
|
|
394
|
+
const grown = new Uint8Array(Math.max(needed, store.length * 2));
|
|
395
|
+
grown.set(this.buffer);
|
|
396
|
+
grown.set(incoming, this.buffer.length);
|
|
397
|
+
this.store = grown;
|
|
398
|
+
this.buffer = grown.subarray(0, needed);
|
|
332
399
|
}
|
|
333
400
|
}
|
|
334
401
|
async next() {
|
|
@@ -370,6 +437,18 @@ class ChunkReader {
|
|
|
370
437
|
}
|
|
371
438
|
}
|
|
372
439
|
}
|
|
440
|
+
const ERROR_TRAILER_PREFIX = "!";
|
|
441
|
+
function errorFromTrailer(payload) {
|
|
442
|
+
let shape;
|
|
443
|
+
try {
|
|
444
|
+
shape = JSON.parse(payload.slice(1));
|
|
445
|
+
} catch {
|
|
446
|
+
shape = null;
|
|
447
|
+
}
|
|
448
|
+
const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
|
|
449
|
+
if (shape && typeof shape.name === "string") error.name = shape.name;
|
|
450
|
+
return error;
|
|
451
|
+
}
|
|
373
452
|
function serializeStream(value, codecOptions) {
|
|
374
453
|
return new ReadableStream({
|
|
375
454
|
async start(controller) {
|
|
@@ -402,11 +481,17 @@ async function deserializeStream(source, codecOptions) {
|
|
|
402
481
|
const reader = new ChunkReader(source.body);
|
|
403
482
|
const result = await reader.next();
|
|
404
483
|
if (!result.done) {
|
|
484
|
+
if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
485
|
+
throw errorFromTrailer(result.value);
|
|
486
|
+
}
|
|
405
487
|
const {
|
|
406
488
|
createJSONDeserializer
|
|
407
489
|
} = await import('@solidjs/web/serialization/decode');
|
|
408
490
|
const deserializeChunk = createJSONDeserializer(codecOptions);
|
|
409
491
|
function interpretChunk(chunk) {
|
|
492
|
+
if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
493
|
+
throw errorFromTrailer(chunk);
|
|
494
|
+
}
|
|
410
495
|
return deserializeChunk(JSON.parse(chunk));
|
|
411
496
|
}
|
|
412
497
|
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
@@ -471,7 +556,8 @@ function serverFunctionUrl(id, boundArgs) {
|
|
|
471
556
|
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
472
557
|
}
|
|
473
558
|
function parseServerFunctionUrl(url) {
|
|
474
|
-
|
|
559
|
+
const parsed = parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
|
|
560
|
+
return parsed && parsed.id;
|
|
475
561
|
}
|
|
476
562
|
function serializeArguments(args) {
|
|
477
563
|
if (!config.serializeArgs) {
|
|
@@ -505,18 +591,41 @@ function provideRPC() {
|
|
|
505
591
|
decodeResponse
|
|
506
592
|
});
|
|
507
593
|
}
|
|
594
|
+
function dataAddressFor(base) {
|
|
595
|
+
const splitAt = base.search(/[?#]/);
|
|
596
|
+
const path = splitAt < 0 ? base : base.slice(0, splitAt);
|
|
597
|
+
const rest = splitAt < 0 ? "" : base.slice(splitAt);
|
|
598
|
+
const slash = path.lastIndexOf("/");
|
|
599
|
+
if (path.endsWith("/data/", slash + 1)) return base;
|
|
600
|
+
return `${path.slice(0, slash + 1)}data/${path.slice(slash + 1)}${rest}`;
|
|
601
|
+
}
|
|
508
602
|
function serverFunctionFailure(response, value) {
|
|
509
|
-
const
|
|
510
|
-
|
|
603
|
+
const unknown = response.headers.get(UNKNOWN_HEADER) !== null;
|
|
604
|
+
const error = value ?? new Error(unknown ? "Server function is not part of the deployment that answered (version skew or removed function)" : `Server function call failed with status ${response.status}`);
|
|
605
|
+
if (error instanceof Error && !("status" in error)) {
|
|
606
|
+
error.status = response.status;
|
|
607
|
+
const retryAfter = parseRetryAfter(response.headers.get("Retry-After"));
|
|
608
|
+
if (retryAfter !== undefined) error.retryAfter = retryAfter;
|
|
609
|
+
}
|
|
610
|
+
if (unknown && error instanceof Error) error.unknownFunction = true;
|
|
511
611
|
return error;
|
|
512
612
|
}
|
|
613
|
+
function parseRetryAfter(header) {
|
|
614
|
+
if (!header) return undefined;
|
|
615
|
+
const trimmed = header.trim();
|
|
616
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed);
|
|
617
|
+
const date = Date.parse(trimmed);
|
|
618
|
+
if (!Number.isNaN(date)) return Math.max(0, Math.ceil((date - Date.now()) / 1000));
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
513
621
|
async function createRequest(base, id, instance, options, meta) {
|
|
514
622
|
const headers = {
|
|
515
623
|
...options.headers,
|
|
516
624
|
[INSTANCE_HEADER]: instance
|
|
517
625
|
};
|
|
518
|
-
|
|
519
|
-
|
|
626
|
+
const flightSources = getFlightDataSourceIds();
|
|
627
|
+
if (flightSources.length > 0 && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
|
|
628
|
+
headers[SINGLE_FLIGHT_HEADER] = flightSources.join(",");
|
|
520
629
|
}
|
|
521
630
|
let init = {
|
|
522
631
|
method: "POST",
|
|
@@ -624,21 +733,24 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
624
733
|
if (response.status >= 400 && !response.headers.has(BODY_FORMAT_HEADER)) {
|
|
625
734
|
throw serverFunctionFailure(response, undefined);
|
|
626
735
|
}
|
|
627
|
-
const failed = response.headers.has(ERROR_HEADER)
|
|
736
|
+
const failed = response.headers.has(ERROR_HEADER);
|
|
628
737
|
if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
629
|
-
const
|
|
630
|
-
|
|
738
|
+
const folded = response.headers.get(SINGLE_FLIGHT_HEADER).split(",");
|
|
739
|
+
const consumers = folded.map(source => [source, getFlightDataConsumer(source)]).filter(([, consumer]) => consumer);
|
|
740
|
+
if (consumers.length > 0) {
|
|
631
741
|
const payload = await decodeResponse(response);
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
742
|
+
for (const [source, consumer] of consumers) {
|
|
743
|
+
await consumer(payload.data[source], {
|
|
744
|
+
response
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
if (failed && !response.headers.has(REDIRECT_HEADER) && !response.headers.has(REVALIDATE_HEADER)) {
|
|
636
748
|
throw serverFunctionFailure(response, payload.value);
|
|
637
749
|
}
|
|
638
750
|
return payload.value;
|
|
639
751
|
}
|
|
640
752
|
}
|
|
641
|
-
if (response.headers.has(
|
|
753
|
+
if (response.headers.has(REDIRECT_HEADER) || response.headers.has(REVALIDATE_HEADER) || response.headers.has(SINGLE_FLIGHT_HEADER) || response.status >= 300 && response.status < 400 && response.status !== 304) {
|
|
642
754
|
return response;
|
|
643
755
|
}
|
|
644
756
|
const result = await decodeResponse(response.clone());
|
|
@@ -676,7 +788,7 @@ function createServerReference(id, name, base) {
|
|
|
676
788
|
});
|
|
677
789
|
if (hit !== undefined) return hit;
|
|
678
790
|
}
|
|
679
|
-
return fetchServerFunction(base
|
|
791
|
+
return fetchServerFunction(base ? dataAddressFor(base) : serverFunctionDataAddress(config.endpoint, id), id, invokeOptions ? {
|
|
680
792
|
...invokeOptions
|
|
681
793
|
} : {}, args, metadata);
|
|
682
794
|
};
|
|
@@ -713,7 +825,7 @@ function GET(fn) {
|
|
|
713
825
|
if (hit !== undefined) return hit;
|
|
714
826
|
}
|
|
715
827
|
const opts = invokeOptions || {};
|
|
716
|
-
const address =
|
|
828
|
+
const address = serverFunctionDataAddress(config.endpoint, id);
|
|
717
829
|
if (!args.length) {
|
|
718
830
|
return fetchServerFunction(address, id, {
|
|
719
831
|
...opts,
|
|
@@ -843,7 +955,7 @@ function live(fn) {
|
|
|
843
955
|
emitClosed(error);
|
|
844
956
|
throw error;
|
|
845
957
|
}
|
|
846
|
-
if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
|
|
958
|
+
if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 425 && error.status !== 429 && typeof error.retryAfter !== "number") {
|
|
847
959
|
stopped = true;
|
|
848
960
|
emitClosed(error);
|
|
849
961
|
throw error;
|
|
@@ -852,7 +964,8 @@ function live(fn) {
|
|
|
852
964
|
emit("reconnecting", error);
|
|
853
965
|
await new Promise(resolve => {
|
|
854
966
|
wake = resolve;
|
|
855
|
-
|
|
967
|
+
const named = error !== null && typeof error === "object" && typeof error.retryAfter === "number" ? Math.min(error.retryAfter * 1000, 60000) : undefined;
|
|
968
|
+
timer = setTimeout(resolve, named ?? Math.min(500 * 2 ** attempts++, 10000));
|
|
856
969
|
if (typeof addEventListener === "function") addEventListener("online", resolve, {
|
|
857
970
|
once: true
|
|
858
971
|
});
|
|
@@ -904,4 +1017,4 @@ function getServerFunctionInvocation() {
|
|
|
904
1017
|
return undefined;
|
|
905
1018
|
}
|
|
906
1019
|
|
|
907
|
-
export { ChunkReader, ERROR_HEADER, FLASH_COOKIE, GET, INSTANCE_HEADER, REVALIDATE_HEADER, SERVER_FUNCTION_INVOKE, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsClient, createChunk, createServerReference, decodeErrorHeaderValue, decodeResponse, decodeResponsePayload, deserializeStream, encodeErrorHeaderValue, frameAddress, getFlightDataConsumer, getServerFunctionInvocation, getServerFunctionMetadata, getServerFunctionsCodec, hasFlashCookie, invoke, isServerFunction, live, observeServerFunctionCalls, parseServerFunctionUrl, registerServerReference, serializeString, serverFunctionUrl, subscribeFlightData, withMeta };
|
|
1020
|
+
export { ChunkReader, ERROR_HEADER, FLASH_COOKIE, GET, INSTANCE_HEADER, REDIRECT_HEADER, REVALIDATE_HEADER, SERVER_FUNCTION_INVOKE, SINGLE_FLIGHT_HEADER, UNKNOWN_HEADER, clearFlashCookie, configureServerFunctionsClient, createChunk, createServerReference, decodeErrorHeaderValue, decodeRedirectHeaderValue, decodeResponse, decodeResponsePayload, deserializeStream, encodeErrorHeaderValue, frameAddress, getFlightDataConsumer, getFlightDataSourceIds, getServerFunctionInvocation, getServerFunctionMetadata, getServerFunctionsCodec, hasFlashCookie, invoke, isServerFunction, live, observeServerFunctionCalls, parseServerFunctionUrl, registerServerReference, serializeString, serverFunctionUrl, subscribeFlightData, withMeta };
|