@solidjs/web 2.0.0-rc.3 → 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 +398 -17
- package/dist/dev.js +396 -19
- package/dist/server.cjs +166 -38
- package/dist/server.js +165 -39
- package/dist/web.cjs +369 -17
- package/dist/web.js +367 -19
- 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 +271 -47
- package/server-functions/dist/client.js +264 -47
- package/server-functions/dist/server.cjs +872 -131
- package/server-functions/dist/server.dev.cjs +892 -131
- package/server-functions/dist/server.dev.js +884 -131
- package/server-functions/dist/server.js +864 -131
- package/types/client.d.ts +4 -2
- 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/index.d.ts +1 -0
- package/types/jsx.d.ts +11 -16
- package/types/patch-driver.d.ts +3 -0
- package/types/response.d.ts +20 -1
- package/types/serializer-decode.d.ts +14 -1
- package/types/serializer.d.ts +7 -0
- package/types/server-functions/client.d.ts +58 -7
- package/types/server-functions/flash.d.ts +9 -0
- package/types/server-functions/registry.d.ts +38 -1
- package/types/server-functions/server.d.ts +66 -14
- package/types/server-functions/shared.d.ts +122 -16
- package/types/server-mock.d.ts +17 -2
- package/types/server.d.ts +16 -2
- package/types-cjs/client.d.cts +4 -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/index.d.cts +1 -0
- package/types-cjs/jsx.d.cts +11 -16
- package/types-cjs/patch-driver.d.cts +3 -0
- package/types-cjs/response.d.cts +20 -1
- 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 +58 -7
- package/types-cjs/server-functions/flash.d.cts +9 -0
- package/types-cjs/server-functions/registry.d.cts +38 -1
- package/types-cjs/server-functions/server.d.cts +66 -14
- package/types-cjs/server-functions/shared.d.cts +122 -16
- package/types-cjs/server-mock.d.cts +17 -2
- package/types-cjs/server.d.cts +16 -2
|
@@ -18,6 +18,32 @@ function withMeta(fn, meta) {
|
|
|
18
18
|
Object.assign(metadata, meta);
|
|
19
19
|
return fn;
|
|
20
20
|
}
|
|
21
|
+
const SERVER_FUNCTION_INVOKE = Symbol.for("solid.ServerFunctionInvoke");
|
|
22
|
+
const INVOKE_OPTION_REDIRECTS = {
|
|
23
|
+
headers: "Session-dynamic headers belong to the prepareRequest hook, declaration metadata to " + "withMeta(fn, meta), and data belongs in the arguments, where it is serialized, typed, " + "and part of the cache key.",
|
|
24
|
+
method: "The method is declaration-scoped: declare the function with GET(fn).",
|
|
25
|
+
body: "The arguments are the body: pass them in the args array.",
|
|
26
|
+
timeout: "Compose timeouts through `signal` with AbortSignal.timeout(ms) (and AbortSignal.any to " + "combine it with a caller signal)."
|
|
27
|
+
};
|
|
28
|
+
function invoke(fn, options, ...args) {
|
|
29
|
+
const channel = typeof fn === "function" && fn[SERVER_FUNCTION_INVOKE];
|
|
30
|
+
if (!channel) {
|
|
31
|
+
throw new Error(isServerFunction(fn) ? "invoke: this wrapper does not forward the invocation channel " + "(SERVER_FUNCTION_INVOKE). Wrappers that share calls (caches, channels) opt in " + "deliberately — a caller's signal cannot own a wire other callers share. Invoke " + "the underlying reference directly, or use the wrapper's own per-call idioms." : "invoke expects a server function reference (or a wrapper that forwards its " + "invocation channel). Per-call options apply at the transport; for a data " + "layer's calls, use its per-call options instead.");
|
|
32
|
+
}
|
|
33
|
+
if (options === null || typeof options !== "object") {
|
|
34
|
+
throw new Error("invoke's second argument is the invocation options bag: invoke(fn, { signal }, ...args)");
|
|
35
|
+
}
|
|
36
|
+
const picked = {};
|
|
37
|
+
for (const key in options) {
|
|
38
|
+
if (key !== "signal" && key !== "keepalive" && key !== "priority") {
|
|
39
|
+
throw new Error(`\`${key}\` is not an invocation option. ` + (INVOKE_OPTION_REDIRECTS[key] || "Options here are strictly invocation-scoped (they vary between calls of the " + "same function): signal, keepalive, priority."));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (options.signal !== undefined) picked.signal = options.signal;
|
|
43
|
+
if (options.keepalive !== undefined) picked.keepalive = options.keepalive;
|
|
44
|
+
if (options.priority !== undefined) picked.priority = options.priority;
|
|
45
|
+
return channel(args, picked);
|
|
46
|
+
}
|
|
21
47
|
const LIVE_SOURCE = Symbol.for("solid.LiveSource");
|
|
22
48
|
const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
|
|
23
49
|
function provideServerFunctionRPC(rpc) {
|
|
@@ -42,17 +68,30 @@ function configureServerFunctionsCodec(codec) {
|
|
|
42
68
|
function getServerFunctionsCodec() {
|
|
43
69
|
return codecConfig.codec;
|
|
44
70
|
}
|
|
71
|
+
const UNNAMED_FLIGHT_SOURCE = "true";
|
|
45
72
|
const flightConfig = {
|
|
46
|
-
|
|
73
|
+
consumers: new Map()
|
|
47
74
|
};
|
|
48
|
-
function
|
|
49
|
-
|
|
75
|
+
function assertFlightSource(source) {
|
|
76
|
+
if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
|
|
77
|
+
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).`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
|
|
81
|
+
const named = typeof sourceOrConsumer === "string";
|
|
82
|
+
if (named) assertFlightSource(sourceOrConsumer);
|
|
83
|
+
const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
|
|
84
|
+
const consumer = named ? maybeConsumer : sourceOrConsumer;
|
|
85
|
+
flightConfig.consumers.set(source, consumer);
|
|
50
86
|
return () => {
|
|
51
|
-
if (flightConfig.
|
|
87
|
+
if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
|
|
52
88
|
};
|
|
53
89
|
}
|
|
54
|
-
function getFlightDataConsumer() {
|
|
55
|
-
return flightConfig.
|
|
90
|
+
function getFlightDataConsumer(source) {
|
|
91
|
+
return flightConfig.consumers.get(source === undefined ? UNNAMED_FLIGHT_SOURCE : source);
|
|
92
|
+
}
|
|
93
|
+
function getFlightDataSourceIds() {
|
|
94
|
+
return [...flightConfig.consumers.keys()];
|
|
56
95
|
}
|
|
57
96
|
function frameAddress(id, args) {
|
|
58
97
|
return args && args.length ? id + ":" + hashArguments(args) : id;
|
|
@@ -98,7 +137,35 @@ function stableString(value, seen) {
|
|
|
98
137
|
}
|
|
99
138
|
return out + "}";
|
|
100
139
|
}
|
|
101
|
-
|
|
140
|
+
function serverFunctionAddress(endpoint, id) {
|
|
141
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
142
|
+
return `${mount}/${encodeURIComponent(id)}`;
|
|
143
|
+
}
|
|
144
|
+
function serverFunctionDataAddress(endpoint, id) {
|
|
145
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
146
|
+
return `${mount}/data/${encodeURIComponent(id)}`;
|
|
147
|
+
}
|
|
148
|
+
function parseServerFunctionAddress(pathname, endpoint) {
|
|
149
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
150
|
+
if (!pathname.startsWith(mount)) return null;
|
|
151
|
+
const rest = pathname.slice(mount.length);
|
|
152
|
+
if (!rest.startsWith("/")) return null;
|
|
153
|
+
let segment = rest.slice(1);
|
|
154
|
+
let data = false;
|
|
155
|
+
if (segment.startsWith("data/")) {
|
|
156
|
+
segment = segment.slice(5);
|
|
157
|
+
data = true;
|
|
158
|
+
}
|
|
159
|
+
if (!segment || segment.includes("/")) return null;
|
|
160
|
+
try {
|
|
161
|
+
return {
|
|
162
|
+
id: decodeURIComponent(segment),
|
|
163
|
+
data
|
|
164
|
+
};
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
102
169
|
const ERROR_HEADER = "X-Server-Function-Error";
|
|
103
170
|
const ERROR_HEADER_MARKER = "=?1?";
|
|
104
171
|
const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
|
|
@@ -126,6 +193,28 @@ function decodeErrorHeaderValue(value) {
|
|
|
126
193
|
}
|
|
127
194
|
const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
128
195
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
196
|
+
const UNKNOWN_HEADER = "X-Server-Function-Unknown";
|
|
197
|
+
const REDIRECT_HEADER = "X-Server-Function-Redirect";
|
|
198
|
+
function decodeRedirectHeaderValue(value) {
|
|
199
|
+
if (typeof value !== "string") return undefined;
|
|
200
|
+
const at = value.indexOf(" ");
|
|
201
|
+
if (at < 0) return undefined;
|
|
202
|
+
const status = Number(value.slice(0, at));
|
|
203
|
+
const url = value.slice(at + 1);
|
|
204
|
+
if (!Number.isInteger(status) || !url) return undefined;
|
|
205
|
+
if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
|
|
206
|
+
let parsed;
|
|
207
|
+
try {
|
|
208
|
+
parsed = new URL(url);
|
|
209
|
+
} catch {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
|
|
213
|
+
return {
|
|
214
|
+
status,
|
|
215
|
+
url
|
|
216
|
+
};
|
|
217
|
+
}
|
|
129
218
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
130
219
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
131
220
|
const BodyFormat = {
|
|
@@ -137,7 +226,8 @@ const BodyFormat = {
|
|
|
137
226
|
File: "5",
|
|
138
227
|
ArrayBuffer: "6",
|
|
139
228
|
Uint8Array: "7",
|
|
140
|
-
Json: "8"
|
|
229
|
+
Json: "8",
|
|
230
|
+
Void: "9"
|
|
141
231
|
};
|
|
142
232
|
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
143
233
|
const EXIT = {};
|
|
@@ -167,7 +257,11 @@ function isJSONSafe(value) {
|
|
|
167
257
|
const proto = Object.getPrototypeOf(v);
|
|
168
258
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
169
259
|
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
170
|
-
for (const k in v)
|
|
260
|
+
for (const k in v) {
|
|
261
|
+
const descriptor = Object.getOwnPropertyDescriptor(v, k);
|
|
262
|
+
if (descriptor === undefined || !("value" in descriptor)) return false;
|
|
263
|
+
stack.push(descriptor.value);
|
|
264
|
+
}
|
|
171
265
|
}
|
|
172
266
|
}
|
|
173
267
|
return true;
|
|
@@ -276,18 +370,34 @@ function createChunk(data) {
|
|
|
276
370
|
class ChunkReader {
|
|
277
371
|
constructor(stream) {
|
|
278
372
|
this.reader = stream.getReader();
|
|
279
|
-
this.
|
|
373
|
+
this.store = new Uint8Array(0);
|
|
374
|
+
this.buffer = this.store;
|
|
280
375
|
this.done = false;
|
|
281
376
|
}
|
|
282
377
|
async readChunk() {
|
|
283
378
|
const chunk = await this.reader.read();
|
|
284
|
-
if (
|
|
285
|
-
const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
|
|
286
|
-
newBuffer.set(this.buffer);
|
|
287
|
-
newBuffer.set(chunk.value, this.buffer.length);
|
|
288
|
-
this.buffer = newBuffer;
|
|
289
|
-
} else {
|
|
379
|
+
if (chunk.done) {
|
|
290
380
|
this.done = true;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const incoming = chunk.value;
|
|
384
|
+
const store = this.store;
|
|
385
|
+
const start = this.buffer.byteOffset;
|
|
386
|
+
const end = start + this.buffer.length;
|
|
387
|
+
const needed = this.buffer.length + incoming.length;
|
|
388
|
+
if (end + incoming.length <= store.length) {
|
|
389
|
+
store.set(incoming, end);
|
|
390
|
+
this.buffer = store.subarray(start, end + incoming.length);
|
|
391
|
+
} else if (needed <= store.length) {
|
|
392
|
+
store.copyWithin(0, start, end);
|
|
393
|
+
store.set(incoming, this.buffer.length);
|
|
394
|
+
this.buffer = store.subarray(0, needed);
|
|
395
|
+
} else {
|
|
396
|
+
const grown = new Uint8Array(Math.max(needed, store.length * 2));
|
|
397
|
+
grown.set(this.buffer);
|
|
398
|
+
grown.set(incoming, this.buffer.length);
|
|
399
|
+
this.store = grown;
|
|
400
|
+
this.buffer = grown.subarray(0, needed);
|
|
291
401
|
}
|
|
292
402
|
}
|
|
293
403
|
async next() {
|
|
@@ -329,6 +439,18 @@ class ChunkReader {
|
|
|
329
439
|
}
|
|
330
440
|
}
|
|
331
441
|
}
|
|
442
|
+
const ERROR_TRAILER_PREFIX = "!";
|
|
443
|
+
function errorFromTrailer(payload) {
|
|
444
|
+
let shape;
|
|
445
|
+
try {
|
|
446
|
+
shape = JSON.parse(payload.slice(1));
|
|
447
|
+
} catch {
|
|
448
|
+
shape = null;
|
|
449
|
+
}
|
|
450
|
+
const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
|
|
451
|
+
if (shape && typeof shape.name === "string") error.name = shape.name;
|
|
452
|
+
return error;
|
|
453
|
+
}
|
|
332
454
|
function serializeStream(value, codecOptions) {
|
|
333
455
|
return new ReadableStream({
|
|
334
456
|
async start(controller) {
|
|
@@ -361,11 +483,17 @@ async function deserializeStream(source, codecOptions) {
|
|
|
361
483
|
const reader = new ChunkReader(source.body);
|
|
362
484
|
const result = await reader.next();
|
|
363
485
|
if (!result.done) {
|
|
486
|
+
if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
487
|
+
throw errorFromTrailer(result.value);
|
|
488
|
+
}
|
|
364
489
|
const {
|
|
365
490
|
createJSONDeserializer
|
|
366
491
|
} = await import('@solidjs/web/serialization/decode');
|
|
367
492
|
const deserializeChunk = createJSONDeserializer(codecOptions);
|
|
368
493
|
function interpretChunk(chunk) {
|
|
494
|
+
if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
495
|
+
throw errorFromTrailer(chunk);
|
|
496
|
+
}
|
|
369
497
|
return deserializeChunk(JSON.parse(chunk));
|
|
370
498
|
}
|
|
371
499
|
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
@@ -392,6 +520,7 @@ async function decodeResponsePayload(response, codecOptions) {
|
|
|
392
520
|
|
|
393
521
|
const config = {
|
|
394
522
|
endpoint: "/_server",
|
|
523
|
+
fetch: undefined,
|
|
395
524
|
prepareRequest: undefined,
|
|
396
525
|
responseHandler: undefined,
|
|
397
526
|
serializeArgs: undefined
|
|
@@ -420,6 +549,18 @@ function observeServerFunctionCalls(observer) {
|
|
|
420
549
|
CALL_OBSERVERS.add(observer);
|
|
421
550
|
return () => CALL_OBSERVERS.delete(observer);
|
|
422
551
|
}
|
|
552
|
+
function serverFunctionUrl(id, boundArgs) {
|
|
553
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
554
|
+
if (!boundArgs || !boundArgs.length) return address;
|
|
555
|
+
if (!isJSONSafe(boundArgs)) {
|
|
556
|
+
throw new Error("Bound arguments in an action url must be JSON-safe: the server reads them the way it " + "reads a form post's, and that convention has no codec. Pass the value through the " + "function's body, or call the reference instead of rendering a url for it.");
|
|
557
|
+
}
|
|
558
|
+
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
559
|
+
}
|
|
560
|
+
function parseServerFunctionUrl(url) {
|
|
561
|
+
const parsed = parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
|
|
562
|
+
return parsed && parsed.id;
|
|
563
|
+
}
|
|
423
564
|
function serializeArguments(args) {
|
|
424
565
|
if (!config.serializeArgs) {
|
|
425
566
|
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.");
|
|
@@ -429,17 +570,20 @@ function serializeArguments(args) {
|
|
|
429
570
|
function configureServerFunctionsClient({
|
|
430
571
|
endpoint,
|
|
431
572
|
codec,
|
|
573
|
+
fetch,
|
|
432
574
|
prepareRequest,
|
|
433
575
|
responseHandler,
|
|
434
576
|
serializeArgs
|
|
435
577
|
} = {}) {
|
|
436
578
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
437
579
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
580
|
+
if (fetch !== undefined) config.fetch = fetch;
|
|
438
581
|
if (prepareRequest !== undefined) config.prepareRequest = prepareRequest;
|
|
439
582
|
if (responseHandler !== undefined) config.responseHandler = responseHandler;
|
|
440
583
|
if (serializeArgs !== undefined) config.serializeArgs = serializeArgs;
|
|
441
584
|
}
|
|
442
585
|
let INSTANCE = 0;
|
|
586
|
+
const MAX_GET_URL_LENGTH = 2000;
|
|
443
587
|
let rpcProvided = false;
|
|
444
588
|
function provideRPC() {
|
|
445
589
|
if (rpcProvided) return;
|
|
@@ -449,19 +593,41 @@ function provideRPC() {
|
|
|
449
593
|
decodeResponse
|
|
450
594
|
});
|
|
451
595
|
}
|
|
596
|
+
function dataAddressFor(base) {
|
|
597
|
+
const splitAt = base.search(/[?#]/);
|
|
598
|
+
const path = splitAt < 0 ? base : base.slice(0, splitAt);
|
|
599
|
+
const rest = splitAt < 0 ? "" : base.slice(splitAt);
|
|
600
|
+
const slash = path.lastIndexOf("/");
|
|
601
|
+
if (path.endsWith("/data/", slash + 1)) return base;
|
|
602
|
+
return `${path.slice(0, slash + 1)}data/${path.slice(slash + 1)}${rest}`;
|
|
603
|
+
}
|
|
452
604
|
function serverFunctionFailure(response, value) {
|
|
453
|
-
const
|
|
454
|
-
|
|
605
|
+
const unknown = response.headers.get(UNKNOWN_HEADER) !== null;
|
|
606
|
+
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}`);
|
|
607
|
+
if (error instanceof Error && !("status" in error)) {
|
|
608
|
+
error.status = response.status;
|
|
609
|
+
const retryAfter = parseRetryAfter(response.headers.get("Retry-After"));
|
|
610
|
+
if (retryAfter !== undefined) error.retryAfter = retryAfter;
|
|
611
|
+
}
|
|
612
|
+
if (unknown && error instanceof Error) error.unknownFunction = true;
|
|
455
613
|
return error;
|
|
456
614
|
}
|
|
615
|
+
function parseRetryAfter(header) {
|
|
616
|
+
if (!header) return undefined;
|
|
617
|
+
const trimmed = header.trim();
|
|
618
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed);
|
|
619
|
+
const date = Date.parse(trimmed);
|
|
620
|
+
if (!Number.isNaN(date)) return Math.max(0, Math.ceil((date - Date.now()) / 1000));
|
|
621
|
+
return undefined;
|
|
622
|
+
}
|
|
457
623
|
async function createRequest(base, id, instance, options, meta) {
|
|
458
624
|
const headers = {
|
|
459
625
|
...options.headers,
|
|
460
|
-
[FUNCTION_HEADER]: id,
|
|
461
626
|
[INSTANCE_HEADER]: instance
|
|
462
627
|
};
|
|
463
|
-
|
|
464
|
-
|
|
628
|
+
const flightSources = getFlightDataSourceIds();
|
|
629
|
+
if (flightSources.length > 0 && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
|
|
630
|
+
headers[SINGLE_FLIGHT_HEADER] = flightSources.join(",");
|
|
465
631
|
}
|
|
466
632
|
let init = {
|
|
467
633
|
method: "POST",
|
|
@@ -474,10 +640,14 @@ async function createRequest(base, id, instance, options, meta) {
|
|
|
474
640
|
meta
|
|
475
641
|
})) || init;
|
|
476
642
|
}
|
|
477
|
-
|
|
478
|
-
|
|
643
|
+
const send = config.fetch || fetch;
|
|
644
|
+
if (CALL_OBSERVERS.size === 0) return send(base, init);
|
|
645
|
+
const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), {
|
|
646
|
+
...init,
|
|
647
|
+
body: init.body instanceof ReadableStream ? undefined : init.body
|
|
648
|
+
});
|
|
479
649
|
notifyCallObservers("request", id, instance, request, meta);
|
|
480
|
-
const response = await
|
|
650
|
+
const response = await send(base, init);
|
|
481
651
|
notifyCallObservers("response", id, instance, response, meta);
|
|
482
652
|
return response;
|
|
483
653
|
}
|
|
@@ -562,21 +732,27 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
562
732
|
});
|
|
563
733
|
if (handled !== undefined) return handled;
|
|
564
734
|
}
|
|
565
|
-
|
|
735
|
+
if (response.status >= 400 && !response.headers.has(BODY_FORMAT_HEADER)) {
|
|
736
|
+
throw serverFunctionFailure(response, undefined);
|
|
737
|
+
}
|
|
738
|
+
const failed = response.headers.has(ERROR_HEADER);
|
|
566
739
|
if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
567
|
-
const
|
|
568
|
-
|
|
740
|
+
const folded = response.headers.get(SINGLE_FLIGHT_HEADER).split(",");
|
|
741
|
+
const consumers = folded.map(source => [source, getFlightDataConsumer(source)]).filter(([, consumer]) => consumer);
|
|
742
|
+
if (consumers.length > 0) {
|
|
569
743
|
const payload = await decodeResponse(response);
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
744
|
+
for (const [source, consumer] of consumers) {
|
|
745
|
+
await consumer(payload.data[source], {
|
|
746
|
+
response
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
if (failed && !response.headers.has(REDIRECT_HEADER) && !response.headers.has(REVALIDATE_HEADER)) {
|
|
574
750
|
throw serverFunctionFailure(response, payload.value);
|
|
575
751
|
}
|
|
576
752
|
return payload.value;
|
|
577
753
|
}
|
|
578
754
|
}
|
|
579
|
-
if (response.headers.has(
|
|
755
|
+
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) {
|
|
580
756
|
return response;
|
|
581
757
|
}
|
|
582
758
|
const result = await decodeResponse(response.clone());
|
|
@@ -604,7 +780,7 @@ function createServerReference(id, name, base) {
|
|
|
604
780
|
const metadata = name === undefined ? {} : {
|
|
605
781
|
name
|
|
606
782
|
};
|
|
607
|
-
const
|
|
783
|
+
const run = (args, invokeOptions) => {
|
|
608
784
|
const handler = config.responseHandler;
|
|
609
785
|
if (handler && handler.intercept) {
|
|
610
786
|
const hit = handler.intercept({
|
|
@@ -614,14 +790,18 @@ function createServerReference(id, name, base) {
|
|
|
614
790
|
});
|
|
615
791
|
if (hit !== undefined) return hit;
|
|
616
792
|
}
|
|
617
|
-
return fetchServerFunction(base
|
|
793
|
+
return fetchServerFunction(base ? dataAddressFor(base) : serverFunctionDataAddress(config.endpoint, id), id, invokeOptions ? {
|
|
794
|
+
...invokeOptions
|
|
795
|
+
} : {}, args, metadata);
|
|
618
796
|
};
|
|
797
|
+
const fn = (...args) => run(args);
|
|
619
798
|
fn[SERVER_FUNCTION_METADATA] = metadata;
|
|
799
|
+
fn[SERVER_FUNCTION_INVOKE] = run;
|
|
620
800
|
return new Proxy(fn, {
|
|
621
801
|
get(target, prop) {
|
|
622
802
|
if (prop === "id") return id;
|
|
623
803
|
if (prop === "url") {
|
|
624
|
-
return base ||
|
|
804
|
+
return base || serverFunctionAddress(config.endpoint, id);
|
|
625
805
|
}
|
|
626
806
|
return target[prop];
|
|
627
807
|
}
|
|
@@ -636,7 +816,7 @@ function GET(fn) {
|
|
|
636
816
|
const metadata = {
|
|
637
817
|
...getServerFunctionMetadata(fn)
|
|
638
818
|
};
|
|
639
|
-
const
|
|
819
|
+
const run = async (args, invokeOptions) => {
|
|
640
820
|
const handler = config.responseHandler;
|
|
641
821
|
if (handler && handler.intercept) {
|
|
642
822
|
const hit = handler.intercept({
|
|
@@ -646,19 +826,34 @@ function GET(fn) {
|
|
|
646
826
|
});
|
|
647
827
|
if (hit !== undefined) return hit;
|
|
648
828
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
829
|
+
const opts = invokeOptions || {};
|
|
830
|
+
const address = serverFunctionDataAddress(config.endpoint, id);
|
|
831
|
+
if (!args.length) {
|
|
832
|
+
return fetchServerFunction(address, id, {
|
|
833
|
+
...opts,
|
|
834
|
+
method: "GET"
|
|
835
|
+
}, [], metadata, args);
|
|
653
836
|
}
|
|
654
|
-
|
|
837
|
+
const encoded = isJSONSafe(args) ? JSON.stringify(args) : await serializeArguments(args);
|
|
838
|
+
const url = `${address}?args=${encodeURIComponent(encoded)}`;
|
|
839
|
+
const absolute = new URL(url, globalThis.location?.href || "http://localhost").href;
|
|
840
|
+
if (absolute.length > MAX_GET_URL_LENGTH) {
|
|
841
|
+
return fetchServerFunction(address, id, {
|
|
842
|
+
...opts,
|
|
843
|
+
read: true
|
|
844
|
+
}, args, metadata);
|
|
845
|
+
}
|
|
846
|
+
return fetchServerFunction(url, id, {
|
|
847
|
+
...opts,
|
|
655
848
|
method: "GET"
|
|
656
849
|
}, [], metadata, args);
|
|
657
850
|
};
|
|
851
|
+
const wrapped = (...args) => run(args);
|
|
658
852
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
853
|
+
wrapped[SERVER_FUNCTION_INVOKE] = run;
|
|
659
854
|
wrapped.id = id;
|
|
660
855
|
Object.defineProperty(wrapped, "url", {
|
|
661
|
-
get: () =>
|
|
856
|
+
get: () => serverFunctionAddress(config.endpoint, id),
|
|
662
857
|
configurable: true
|
|
663
858
|
});
|
|
664
859
|
return withMeta(wrapped, {
|
|
@@ -674,7 +869,7 @@ function live(fn) {
|
|
|
674
869
|
...getServerFunctionMetadata(fn),
|
|
675
870
|
live: true
|
|
676
871
|
};
|
|
677
|
-
const
|
|
872
|
+
const makeIterable = (args, invokeOptions) => {
|
|
678
873
|
const iterable = {
|
|
679
874
|
[LIVE_SOURCE]: true,
|
|
680
875
|
[Symbol.asyncIterator]() {
|
|
@@ -688,6 +883,12 @@ function live(fn) {
|
|
|
688
883
|
done: true,
|
|
689
884
|
value: undefined
|
|
690
885
|
};
|
|
886
|
+
const invokeSignal = invokeOptions && invokeOptions.signal;
|
|
887
|
+
const controller = new AbortController();
|
|
888
|
+
const wireOptions = {
|
|
889
|
+
...invokeOptions,
|
|
890
|
+
signal: invokeSignal ? AbortSignal.any([invokeSignal, controller.signal]) : controller.signal
|
|
891
|
+
};
|
|
691
892
|
const emit = (state, error) => {
|
|
692
893
|
try {
|
|
693
894
|
iterable.onstatus && iterable.onstatus(state, error);
|
|
@@ -709,7 +910,7 @@ function live(fn) {
|
|
|
709
910
|
}
|
|
710
911
|
};
|
|
711
912
|
const callOnce = () => {
|
|
712
|
-
if (metadata.method === "GET") return fn(
|
|
913
|
+
if (metadata.method === "GET") return fn[SERVER_FUNCTION_INVOKE](args, wireOptions);
|
|
713
914
|
const handler = config.responseHandler;
|
|
714
915
|
if (handler && handler.intercept) {
|
|
715
916
|
const hit = handler.intercept({
|
|
@@ -720,6 +921,7 @@ function live(fn) {
|
|
|
720
921
|
if (hit !== undefined) return hit;
|
|
721
922
|
}
|
|
722
923
|
return fetchServerFunction(fn.url, id, {
|
|
924
|
+
...wireOptions,
|
|
723
925
|
read: true
|
|
724
926
|
}, args, metadata, args);
|
|
725
927
|
};
|
|
@@ -734,6 +936,7 @@ function live(fn) {
|
|
|
734
936
|
}();
|
|
735
937
|
if (stopped) {
|
|
736
938
|
closeIt();
|
|
939
|
+
controller.abort();
|
|
737
940
|
return DONE;
|
|
738
941
|
}
|
|
739
942
|
emit("connected");
|
|
@@ -747,8 +950,14 @@ function live(fn) {
|
|
|
747
950
|
attempts = 0;
|
|
748
951
|
return r;
|
|
749
952
|
} catch (error) {
|
|
953
|
+
if (stopped) return DONE;
|
|
750
954
|
if (!connected) throw error;
|
|
751
|
-
if (
|
|
955
|
+
if (invokeSignal && invokeSignal.aborted) {
|
|
956
|
+
stopped = true;
|
|
957
|
+
emitClosed(error);
|
|
958
|
+
throw error;
|
|
959
|
+
}
|
|
960
|
+
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") {
|
|
752
961
|
stopped = true;
|
|
753
962
|
emitClosed(error);
|
|
754
963
|
throw error;
|
|
@@ -757,13 +966,18 @@ function live(fn) {
|
|
|
757
966
|
emit("reconnecting", error);
|
|
758
967
|
await new Promise(resolve => {
|
|
759
968
|
wake = resolve;
|
|
760
|
-
|
|
969
|
+
const named = error !== null && typeof error === "object" && typeof error.retryAfter === "number" ? Math.min(error.retryAfter * 1000, 60000) : undefined;
|
|
970
|
+
timer = setTimeout(resolve, named ?? Math.min(500 * 2 ** attempts++, 10000));
|
|
761
971
|
if (typeof addEventListener === "function") addEventListener("online", resolve, {
|
|
762
972
|
once: true
|
|
763
973
|
});
|
|
974
|
+
if (invokeSignal) invokeSignal.addEventListener("abort", resolve, {
|
|
975
|
+
once: true
|
|
976
|
+
});
|
|
764
977
|
});
|
|
765
978
|
clearTimeout(timer);
|
|
766
979
|
if (typeof removeEventListener === "function") removeEventListener("online", wake);
|
|
980
|
+
if (invokeSignal) invokeSignal.removeEventListener("abort", wake);
|
|
767
981
|
timer = wake = undefined;
|
|
768
982
|
}
|
|
769
983
|
}
|
|
@@ -776,6 +990,7 @@ function live(fn) {
|
|
|
776
990
|
if (timer !== undefined) clearTimeout(timer);
|
|
777
991
|
if (wake) wake();
|
|
778
992
|
closeIt(value);
|
|
993
|
+
controller.abort();
|
|
779
994
|
emitClosed();
|
|
780
995
|
return Promise.resolve({
|
|
781
996
|
done: true,
|
|
@@ -787,7 +1002,9 @@ function live(fn) {
|
|
|
787
1002
|
};
|
|
788
1003
|
return iterable;
|
|
789
1004
|
};
|
|
1005
|
+
const wrapped = (...args) => makeIterable(args);
|
|
790
1006
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
1007
|
+
wrapped[SERVER_FUNCTION_INVOKE] = makeIterable;
|
|
791
1008
|
wrapped.id = id;
|
|
792
1009
|
Object.defineProperty(wrapped, "url", {
|
|
793
1010
|
get: () => fn.url,
|
|
@@ -805,30 +1022,37 @@ function getServerFunctionInvocation() {
|
|
|
805
1022
|
exports.ChunkReader = ChunkReader;
|
|
806
1023
|
exports.ERROR_HEADER = ERROR_HEADER;
|
|
807
1024
|
exports.FLASH_COOKIE = FLASH_COOKIE;
|
|
808
|
-
exports.FUNCTION_HEADER = FUNCTION_HEADER;
|
|
809
1025
|
exports.GET = GET;
|
|
810
1026
|
exports.INSTANCE_HEADER = INSTANCE_HEADER;
|
|
1027
|
+
exports.REDIRECT_HEADER = REDIRECT_HEADER;
|
|
811
1028
|
exports.REVALIDATE_HEADER = REVALIDATE_HEADER;
|
|
1029
|
+
exports.SERVER_FUNCTION_INVOKE = SERVER_FUNCTION_INVOKE;
|
|
812
1030
|
exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
|
|
1031
|
+
exports.UNKNOWN_HEADER = UNKNOWN_HEADER;
|
|
813
1032
|
exports.clearFlashCookie = clearFlashCookie;
|
|
814
1033
|
exports.configureServerFunctionsClient = configureServerFunctionsClient;
|
|
815
1034
|
exports.createChunk = createChunk;
|
|
816
1035
|
exports.createServerReference = createServerReference;
|
|
817
1036
|
exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
|
|
1037
|
+
exports.decodeRedirectHeaderValue = decodeRedirectHeaderValue;
|
|
818
1038
|
exports.decodeResponse = decodeResponse;
|
|
819
1039
|
exports.decodeResponsePayload = decodeResponsePayload;
|
|
820
1040
|
exports.deserializeStream = deserializeStream;
|
|
821
1041
|
exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
|
|
822
1042
|
exports.frameAddress = frameAddress;
|
|
823
1043
|
exports.getFlightDataConsumer = getFlightDataConsumer;
|
|
1044
|
+
exports.getFlightDataSourceIds = getFlightDataSourceIds;
|
|
824
1045
|
exports.getServerFunctionInvocation = getServerFunctionInvocation;
|
|
825
1046
|
exports.getServerFunctionMetadata = getServerFunctionMetadata;
|
|
826
1047
|
exports.getServerFunctionsCodec = getServerFunctionsCodec;
|
|
827
1048
|
exports.hasFlashCookie = hasFlashCookie;
|
|
1049
|
+
exports.invoke = invoke;
|
|
828
1050
|
exports.isServerFunction = isServerFunction;
|
|
829
1051
|
exports.live = live;
|
|
830
1052
|
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
1053
|
+
exports.parseServerFunctionUrl = parseServerFunctionUrl;
|
|
831
1054
|
exports.registerServerReference = registerServerReference;
|
|
832
1055
|
exports.serializeString = serializeString;
|
|
1056
|
+
exports.serverFunctionUrl = serverFunctionUrl;
|
|
833
1057
|
exports.subscribeFlightData = subscribeFlightData;
|
|
834
1058
|
exports.withMeta = withMeta;
|