@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
|
@@ -16,6 +16,32 @@ function withMeta(fn, meta) {
|
|
|
16
16
|
Object.assign(metadata, meta);
|
|
17
17
|
return fn;
|
|
18
18
|
}
|
|
19
|
+
const SERVER_FUNCTION_INVOKE = Symbol.for("solid.ServerFunctionInvoke");
|
|
20
|
+
const INVOKE_OPTION_REDIRECTS = {
|
|
21
|
+
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.",
|
|
22
|
+
method: "The method is declaration-scoped: declare the function with GET(fn).",
|
|
23
|
+
body: "The arguments are the body: pass them in the args array.",
|
|
24
|
+
timeout: "Compose timeouts through `signal` with AbortSignal.timeout(ms) (and AbortSignal.any to " + "combine it with a caller signal)."
|
|
25
|
+
};
|
|
26
|
+
function invoke(fn, options, ...args) {
|
|
27
|
+
const channel = typeof fn === "function" && fn[SERVER_FUNCTION_INVOKE];
|
|
28
|
+
if (!channel) {
|
|
29
|
+
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.");
|
|
30
|
+
}
|
|
31
|
+
if (options === null || typeof options !== "object") {
|
|
32
|
+
throw new Error("invoke's second argument is the invocation options bag: invoke(fn, { signal }, ...args)");
|
|
33
|
+
}
|
|
34
|
+
const picked = {};
|
|
35
|
+
for (const key in options) {
|
|
36
|
+
if (key !== "signal" && key !== "keepalive" && key !== "priority") {
|
|
37
|
+
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."));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (options.signal !== undefined) picked.signal = options.signal;
|
|
41
|
+
if (options.keepalive !== undefined) picked.keepalive = options.keepalive;
|
|
42
|
+
if (options.priority !== undefined) picked.priority = options.priority;
|
|
43
|
+
return channel(args, picked);
|
|
44
|
+
}
|
|
19
45
|
const LIVE_SOURCE = Symbol.for("solid.LiveSource");
|
|
20
46
|
const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
|
|
21
47
|
function provideServerFunctionRPC(rpc) {
|
|
@@ -40,17 +66,30 @@ function configureServerFunctionsCodec(codec) {
|
|
|
40
66
|
function getServerFunctionsCodec() {
|
|
41
67
|
return codecConfig.codec;
|
|
42
68
|
}
|
|
69
|
+
const UNNAMED_FLIGHT_SOURCE = "true";
|
|
43
70
|
const flightConfig = {
|
|
44
|
-
|
|
71
|
+
consumers: new Map()
|
|
45
72
|
};
|
|
46
|
-
function
|
|
47
|
-
|
|
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);
|
|
48
84
|
return () => {
|
|
49
|
-
if (flightConfig.
|
|
85
|
+
if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
|
|
50
86
|
};
|
|
51
87
|
}
|
|
52
|
-
function getFlightDataConsumer() {
|
|
53
|
-
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()];
|
|
54
93
|
}
|
|
55
94
|
function frameAddress(id, args) {
|
|
56
95
|
return args && args.length ? id + ":" + hashArguments(args) : id;
|
|
@@ -96,7 +135,35 @@ function stableString(value, seen) {
|
|
|
96
135
|
}
|
|
97
136
|
return out + "}";
|
|
98
137
|
}
|
|
99
|
-
|
|
138
|
+
function serverFunctionAddress(endpoint, id) {
|
|
139
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
140
|
+
return `${mount}/${encodeURIComponent(id)}`;
|
|
141
|
+
}
|
|
142
|
+
function serverFunctionDataAddress(endpoint, id) {
|
|
143
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
144
|
+
return `${mount}/data/${encodeURIComponent(id)}`;
|
|
145
|
+
}
|
|
146
|
+
function parseServerFunctionAddress(pathname, endpoint) {
|
|
147
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
148
|
+
if (!pathname.startsWith(mount)) return null;
|
|
149
|
+
const rest = pathname.slice(mount.length);
|
|
150
|
+
if (!rest.startsWith("/")) return null;
|
|
151
|
+
let segment = rest.slice(1);
|
|
152
|
+
let data = false;
|
|
153
|
+
if (segment.startsWith("data/")) {
|
|
154
|
+
segment = segment.slice(5);
|
|
155
|
+
data = true;
|
|
156
|
+
}
|
|
157
|
+
if (!segment || segment.includes("/")) return null;
|
|
158
|
+
try {
|
|
159
|
+
return {
|
|
160
|
+
id: decodeURIComponent(segment),
|
|
161
|
+
data
|
|
162
|
+
};
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
100
167
|
const ERROR_HEADER = "X-Server-Function-Error";
|
|
101
168
|
const ERROR_HEADER_MARKER = "=?1?";
|
|
102
169
|
const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
|
|
@@ -124,6 +191,28 @@ function decodeErrorHeaderValue(value) {
|
|
|
124
191
|
}
|
|
125
192
|
const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
126
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
|
+
}
|
|
127
216
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
128
217
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
129
218
|
const BodyFormat = {
|
|
@@ -135,7 +224,8 @@ const BodyFormat = {
|
|
|
135
224
|
File: "5",
|
|
136
225
|
ArrayBuffer: "6",
|
|
137
226
|
Uint8Array: "7",
|
|
138
|
-
Json: "8"
|
|
227
|
+
Json: "8",
|
|
228
|
+
Void: "9"
|
|
139
229
|
};
|
|
140
230
|
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
141
231
|
const EXIT = {};
|
|
@@ -165,7 +255,11 @@ function isJSONSafe(value) {
|
|
|
165
255
|
const proto = Object.getPrototypeOf(v);
|
|
166
256
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
167
257
|
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
168
|
-
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
|
+
}
|
|
169
263
|
}
|
|
170
264
|
}
|
|
171
265
|
return true;
|
|
@@ -274,18 +368,34 @@ function createChunk(data) {
|
|
|
274
368
|
class ChunkReader {
|
|
275
369
|
constructor(stream) {
|
|
276
370
|
this.reader = stream.getReader();
|
|
277
|
-
this.
|
|
371
|
+
this.store = new Uint8Array(0);
|
|
372
|
+
this.buffer = this.store;
|
|
278
373
|
this.done = false;
|
|
279
374
|
}
|
|
280
375
|
async readChunk() {
|
|
281
376
|
const chunk = await this.reader.read();
|
|
282
|
-
if (
|
|
283
|
-
const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
|
|
284
|
-
newBuffer.set(this.buffer);
|
|
285
|
-
newBuffer.set(chunk.value, this.buffer.length);
|
|
286
|
-
this.buffer = newBuffer;
|
|
287
|
-
} else {
|
|
377
|
+
if (chunk.done) {
|
|
288
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);
|
|
289
399
|
}
|
|
290
400
|
}
|
|
291
401
|
async next() {
|
|
@@ -327,6 +437,18 @@ class ChunkReader {
|
|
|
327
437
|
}
|
|
328
438
|
}
|
|
329
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
|
+
}
|
|
330
452
|
function serializeStream(value, codecOptions) {
|
|
331
453
|
return new ReadableStream({
|
|
332
454
|
async start(controller) {
|
|
@@ -359,11 +481,17 @@ async function deserializeStream(source, codecOptions) {
|
|
|
359
481
|
const reader = new ChunkReader(source.body);
|
|
360
482
|
const result = await reader.next();
|
|
361
483
|
if (!result.done) {
|
|
484
|
+
if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
485
|
+
throw errorFromTrailer(result.value);
|
|
486
|
+
}
|
|
362
487
|
const {
|
|
363
488
|
createJSONDeserializer
|
|
364
489
|
} = await import('@solidjs/web/serialization/decode');
|
|
365
490
|
const deserializeChunk = createJSONDeserializer(codecOptions);
|
|
366
491
|
function interpretChunk(chunk) {
|
|
492
|
+
if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
493
|
+
throw errorFromTrailer(chunk);
|
|
494
|
+
}
|
|
367
495
|
return deserializeChunk(JSON.parse(chunk));
|
|
368
496
|
}
|
|
369
497
|
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
@@ -390,6 +518,7 @@ async function decodeResponsePayload(response, codecOptions) {
|
|
|
390
518
|
|
|
391
519
|
const config = {
|
|
392
520
|
endpoint: "/_server",
|
|
521
|
+
fetch: undefined,
|
|
393
522
|
prepareRequest: undefined,
|
|
394
523
|
responseHandler: undefined,
|
|
395
524
|
serializeArgs: undefined
|
|
@@ -418,6 +547,18 @@ function observeServerFunctionCalls(observer) {
|
|
|
418
547
|
CALL_OBSERVERS.add(observer);
|
|
419
548
|
return () => CALL_OBSERVERS.delete(observer);
|
|
420
549
|
}
|
|
550
|
+
function serverFunctionUrl(id, boundArgs) {
|
|
551
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
552
|
+
if (!boundArgs || !boundArgs.length) return address;
|
|
553
|
+
if (!isJSONSafe(boundArgs)) {
|
|
554
|
+
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.");
|
|
555
|
+
}
|
|
556
|
+
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
557
|
+
}
|
|
558
|
+
function parseServerFunctionUrl(url) {
|
|
559
|
+
const parsed = parseServerFunctionAddress(new URL(url, globalThis.location?.href || "http://localhost").pathname, config.endpoint);
|
|
560
|
+
return parsed && parsed.id;
|
|
561
|
+
}
|
|
421
562
|
function serializeArguments(args) {
|
|
422
563
|
if (!config.serializeArgs) {
|
|
423
564
|
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.");
|
|
@@ -427,17 +568,20 @@ function serializeArguments(args) {
|
|
|
427
568
|
function configureServerFunctionsClient({
|
|
428
569
|
endpoint,
|
|
429
570
|
codec,
|
|
571
|
+
fetch,
|
|
430
572
|
prepareRequest,
|
|
431
573
|
responseHandler,
|
|
432
574
|
serializeArgs
|
|
433
575
|
} = {}) {
|
|
434
576
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
435
577
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
578
|
+
if (fetch !== undefined) config.fetch = fetch;
|
|
436
579
|
if (prepareRequest !== undefined) config.prepareRequest = prepareRequest;
|
|
437
580
|
if (responseHandler !== undefined) config.responseHandler = responseHandler;
|
|
438
581
|
if (serializeArgs !== undefined) config.serializeArgs = serializeArgs;
|
|
439
582
|
}
|
|
440
583
|
let INSTANCE = 0;
|
|
584
|
+
const MAX_GET_URL_LENGTH = 2000;
|
|
441
585
|
let rpcProvided = false;
|
|
442
586
|
function provideRPC() {
|
|
443
587
|
if (rpcProvided) return;
|
|
@@ -447,19 +591,41 @@ function provideRPC() {
|
|
|
447
591
|
decodeResponse
|
|
448
592
|
});
|
|
449
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
|
+
}
|
|
450
602
|
function serverFunctionFailure(response, value) {
|
|
451
|
-
const
|
|
452
|
-
|
|
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;
|
|
453
611
|
return error;
|
|
454
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
|
+
}
|
|
455
621
|
async function createRequest(base, id, instance, options, meta) {
|
|
456
622
|
const headers = {
|
|
457
623
|
...options.headers,
|
|
458
|
-
[FUNCTION_HEADER]: id,
|
|
459
624
|
[INSTANCE_HEADER]: instance
|
|
460
625
|
};
|
|
461
|
-
|
|
462
|
-
|
|
626
|
+
const flightSources = getFlightDataSourceIds();
|
|
627
|
+
if (flightSources.length > 0 && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
|
|
628
|
+
headers[SINGLE_FLIGHT_HEADER] = flightSources.join(",");
|
|
463
629
|
}
|
|
464
630
|
let init = {
|
|
465
631
|
method: "POST",
|
|
@@ -472,10 +638,14 @@ async function createRequest(base, id, instance, options, meta) {
|
|
|
472
638
|
meta
|
|
473
639
|
})) || init;
|
|
474
640
|
}
|
|
475
|
-
|
|
476
|
-
|
|
641
|
+
const send = config.fetch || fetch;
|
|
642
|
+
if (CALL_OBSERVERS.size === 0) return send(base, init);
|
|
643
|
+
const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), {
|
|
644
|
+
...init,
|
|
645
|
+
body: init.body instanceof ReadableStream ? undefined : init.body
|
|
646
|
+
});
|
|
477
647
|
notifyCallObservers("request", id, instance, request, meta);
|
|
478
|
-
const response = await
|
|
648
|
+
const response = await send(base, init);
|
|
479
649
|
notifyCallObservers("response", id, instance, response, meta);
|
|
480
650
|
return response;
|
|
481
651
|
}
|
|
@@ -560,21 +730,27 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
|
|
|
560
730
|
});
|
|
561
731
|
if (handled !== undefined) return handled;
|
|
562
732
|
}
|
|
563
|
-
|
|
733
|
+
if (response.status >= 400 && !response.headers.has(BODY_FORMAT_HEADER)) {
|
|
734
|
+
throw serverFunctionFailure(response, undefined);
|
|
735
|
+
}
|
|
736
|
+
const failed = response.headers.has(ERROR_HEADER);
|
|
564
737
|
if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
|
|
565
|
-
const
|
|
566
|
-
|
|
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) {
|
|
567
741
|
const payload = await decodeResponse(response);
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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)) {
|
|
572
748
|
throw serverFunctionFailure(response, payload.value);
|
|
573
749
|
}
|
|
574
750
|
return payload.value;
|
|
575
751
|
}
|
|
576
752
|
}
|
|
577
|
-
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) {
|
|
578
754
|
return response;
|
|
579
755
|
}
|
|
580
756
|
const result = await decodeResponse(response.clone());
|
|
@@ -602,7 +778,7 @@ function createServerReference(id, name, base) {
|
|
|
602
778
|
const metadata = name === undefined ? {} : {
|
|
603
779
|
name
|
|
604
780
|
};
|
|
605
|
-
const
|
|
781
|
+
const run = (args, invokeOptions) => {
|
|
606
782
|
const handler = config.responseHandler;
|
|
607
783
|
if (handler && handler.intercept) {
|
|
608
784
|
const hit = handler.intercept({
|
|
@@ -612,14 +788,18 @@ function createServerReference(id, name, base) {
|
|
|
612
788
|
});
|
|
613
789
|
if (hit !== undefined) return hit;
|
|
614
790
|
}
|
|
615
|
-
return fetchServerFunction(base
|
|
791
|
+
return fetchServerFunction(base ? dataAddressFor(base) : serverFunctionDataAddress(config.endpoint, id), id, invokeOptions ? {
|
|
792
|
+
...invokeOptions
|
|
793
|
+
} : {}, args, metadata);
|
|
616
794
|
};
|
|
795
|
+
const fn = (...args) => run(args);
|
|
617
796
|
fn[SERVER_FUNCTION_METADATA] = metadata;
|
|
797
|
+
fn[SERVER_FUNCTION_INVOKE] = run;
|
|
618
798
|
return new Proxy(fn, {
|
|
619
799
|
get(target, prop) {
|
|
620
800
|
if (prop === "id") return id;
|
|
621
801
|
if (prop === "url") {
|
|
622
|
-
return base ||
|
|
802
|
+
return base || serverFunctionAddress(config.endpoint, id);
|
|
623
803
|
}
|
|
624
804
|
return target[prop];
|
|
625
805
|
}
|
|
@@ -634,7 +814,7 @@ function GET(fn) {
|
|
|
634
814
|
const metadata = {
|
|
635
815
|
...getServerFunctionMetadata(fn)
|
|
636
816
|
};
|
|
637
|
-
const
|
|
817
|
+
const run = async (args, invokeOptions) => {
|
|
638
818
|
const handler = config.responseHandler;
|
|
639
819
|
if (handler && handler.intercept) {
|
|
640
820
|
const hit = handler.intercept({
|
|
@@ -644,19 +824,34 @@ function GET(fn) {
|
|
|
644
824
|
});
|
|
645
825
|
if (hit !== undefined) return hit;
|
|
646
826
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
827
|
+
const opts = invokeOptions || {};
|
|
828
|
+
const address = serverFunctionDataAddress(config.endpoint, id);
|
|
829
|
+
if (!args.length) {
|
|
830
|
+
return fetchServerFunction(address, id, {
|
|
831
|
+
...opts,
|
|
832
|
+
method: "GET"
|
|
833
|
+
}, [], metadata, args);
|
|
651
834
|
}
|
|
652
|
-
|
|
835
|
+
const encoded = isJSONSafe(args) ? JSON.stringify(args) : await serializeArguments(args);
|
|
836
|
+
const url = `${address}?args=${encodeURIComponent(encoded)}`;
|
|
837
|
+
const absolute = new URL(url, globalThis.location?.href || "http://localhost").href;
|
|
838
|
+
if (absolute.length > MAX_GET_URL_LENGTH) {
|
|
839
|
+
return fetchServerFunction(address, id, {
|
|
840
|
+
...opts,
|
|
841
|
+
read: true
|
|
842
|
+
}, args, metadata);
|
|
843
|
+
}
|
|
844
|
+
return fetchServerFunction(url, id, {
|
|
845
|
+
...opts,
|
|
653
846
|
method: "GET"
|
|
654
847
|
}, [], metadata, args);
|
|
655
848
|
};
|
|
849
|
+
const wrapped = (...args) => run(args);
|
|
656
850
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
851
|
+
wrapped[SERVER_FUNCTION_INVOKE] = run;
|
|
657
852
|
wrapped.id = id;
|
|
658
853
|
Object.defineProperty(wrapped, "url", {
|
|
659
|
-
get: () =>
|
|
854
|
+
get: () => serverFunctionAddress(config.endpoint, id),
|
|
660
855
|
configurable: true
|
|
661
856
|
});
|
|
662
857
|
return withMeta(wrapped, {
|
|
@@ -672,7 +867,7 @@ function live(fn) {
|
|
|
672
867
|
...getServerFunctionMetadata(fn),
|
|
673
868
|
live: true
|
|
674
869
|
};
|
|
675
|
-
const
|
|
870
|
+
const makeIterable = (args, invokeOptions) => {
|
|
676
871
|
const iterable = {
|
|
677
872
|
[LIVE_SOURCE]: true,
|
|
678
873
|
[Symbol.asyncIterator]() {
|
|
@@ -686,6 +881,12 @@ function live(fn) {
|
|
|
686
881
|
done: true,
|
|
687
882
|
value: undefined
|
|
688
883
|
};
|
|
884
|
+
const invokeSignal = invokeOptions && invokeOptions.signal;
|
|
885
|
+
const controller = new AbortController();
|
|
886
|
+
const wireOptions = {
|
|
887
|
+
...invokeOptions,
|
|
888
|
+
signal: invokeSignal ? AbortSignal.any([invokeSignal, controller.signal]) : controller.signal
|
|
889
|
+
};
|
|
689
890
|
const emit = (state, error) => {
|
|
690
891
|
try {
|
|
691
892
|
iterable.onstatus && iterable.onstatus(state, error);
|
|
@@ -707,7 +908,7 @@ function live(fn) {
|
|
|
707
908
|
}
|
|
708
909
|
};
|
|
709
910
|
const callOnce = () => {
|
|
710
|
-
if (metadata.method === "GET") return fn(
|
|
911
|
+
if (metadata.method === "GET") return fn[SERVER_FUNCTION_INVOKE](args, wireOptions);
|
|
711
912
|
const handler = config.responseHandler;
|
|
712
913
|
if (handler && handler.intercept) {
|
|
713
914
|
const hit = handler.intercept({
|
|
@@ -718,6 +919,7 @@ function live(fn) {
|
|
|
718
919
|
if (hit !== undefined) return hit;
|
|
719
920
|
}
|
|
720
921
|
return fetchServerFunction(fn.url, id, {
|
|
922
|
+
...wireOptions,
|
|
721
923
|
read: true
|
|
722
924
|
}, args, metadata, args);
|
|
723
925
|
};
|
|
@@ -732,6 +934,7 @@ function live(fn) {
|
|
|
732
934
|
}();
|
|
733
935
|
if (stopped) {
|
|
734
936
|
closeIt();
|
|
937
|
+
controller.abort();
|
|
735
938
|
return DONE;
|
|
736
939
|
}
|
|
737
940
|
emit("connected");
|
|
@@ -745,8 +948,14 @@ function live(fn) {
|
|
|
745
948
|
attempts = 0;
|
|
746
949
|
return r;
|
|
747
950
|
} catch (error) {
|
|
951
|
+
if (stopped) return DONE;
|
|
748
952
|
if (!connected) throw error;
|
|
749
|
-
if (
|
|
953
|
+
if (invokeSignal && invokeSignal.aborted) {
|
|
954
|
+
stopped = true;
|
|
955
|
+
emitClosed(error);
|
|
956
|
+
throw error;
|
|
957
|
+
}
|
|
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") {
|
|
750
959
|
stopped = true;
|
|
751
960
|
emitClosed(error);
|
|
752
961
|
throw error;
|
|
@@ -755,13 +964,18 @@ function live(fn) {
|
|
|
755
964
|
emit("reconnecting", error);
|
|
756
965
|
await new Promise(resolve => {
|
|
757
966
|
wake = resolve;
|
|
758
|
-
|
|
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));
|
|
759
969
|
if (typeof addEventListener === "function") addEventListener("online", resolve, {
|
|
760
970
|
once: true
|
|
761
971
|
});
|
|
972
|
+
if (invokeSignal) invokeSignal.addEventListener("abort", resolve, {
|
|
973
|
+
once: true
|
|
974
|
+
});
|
|
762
975
|
});
|
|
763
976
|
clearTimeout(timer);
|
|
764
977
|
if (typeof removeEventListener === "function") removeEventListener("online", wake);
|
|
978
|
+
if (invokeSignal) invokeSignal.removeEventListener("abort", wake);
|
|
765
979
|
timer = wake = undefined;
|
|
766
980
|
}
|
|
767
981
|
}
|
|
@@ -774,6 +988,7 @@ function live(fn) {
|
|
|
774
988
|
if (timer !== undefined) clearTimeout(timer);
|
|
775
989
|
if (wake) wake();
|
|
776
990
|
closeIt(value);
|
|
991
|
+
controller.abort();
|
|
777
992
|
emitClosed();
|
|
778
993
|
return Promise.resolve({
|
|
779
994
|
done: true,
|
|
@@ -785,7 +1000,9 @@ function live(fn) {
|
|
|
785
1000
|
};
|
|
786
1001
|
return iterable;
|
|
787
1002
|
};
|
|
1003
|
+
const wrapped = (...args) => makeIterable(args);
|
|
788
1004
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
1005
|
+
wrapped[SERVER_FUNCTION_INVOKE] = makeIterable;
|
|
789
1006
|
wrapped.id = id;
|
|
790
1007
|
Object.defineProperty(wrapped, "url", {
|
|
791
1008
|
get: () => fn.url,
|
|
@@ -800,4 +1017,4 @@ function getServerFunctionInvocation() {
|
|
|
800
1017
|
return undefined;
|
|
801
1018
|
}
|
|
802
1019
|
|
|
803
|
-
export { ChunkReader, ERROR_HEADER, FLASH_COOKIE,
|
|
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 };
|