@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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/dist/dev.cjs +40 -11
  3. package/dist/dev.js +39 -12
  4. package/dist/server.cjs +419 -69
  5. package/dist/server.js +411 -73
  6. package/dist/web.cjs +37 -11
  7. package/dist/web.js +36 -12
  8. package/frames/dist/client.cjs +94 -8
  9. package/frames/dist/client.dev.cjs +97 -8
  10. package/frames/dist/client.dev.js +98 -9
  11. package/frames/dist/client.js +95 -9
  12. package/frames/dist/server.cjs +266 -56
  13. package/frames/dist/server.js +267 -57
  14. package/package.json +4 -3
  15. package/serialization/dist/decode.cjs +32 -3
  16. package/serialization/dist/decode.js +33 -4
  17. package/serialization/dist/serialization.cjs +32 -3
  18. package/serialization/dist/serialization.js +33 -4
  19. package/server-functions/dist/client.cjs +196 -8
  20. package/server-functions/dist/client.js +195 -9
  21. package/server-functions/dist/server.cjs +201 -36
  22. package/server-functions/dist/server.dev.cjs +201 -36
  23. package/server-functions/dist/server.dev.js +199 -37
  24. package/server-functions/dist/server.js +199 -37
  25. package/types/core.d.ts +3 -0
  26. package/types/frames/frame-client.d.ts +18 -0
  27. package/types/index.d.ts +16 -2
  28. package/types/jsx.d.ts +9 -0
  29. package/types/server-functions/client.d.ts +61 -0
  30. package/types/server-functions/server.d.ts +94 -1
  31. package/types/server-mock.d.ts +11 -2
  32. package/types/server.d.ts +23 -2
  33. package/types-cjs/core.d.cts +3 -0
  34. package/types-cjs/frames/frame-client.d.cts +18 -0
  35. package/types-cjs/index.d.cts +16 -2
  36. package/types-cjs/jsx.d.cts +9 -0
  37. package/types-cjs/server-functions/client.d.cts +61 -0
  38. package/types-cjs/server-functions/server.d.cts +94 -1
  39. package/types-cjs/server-mock.d.cts +11 -2
  40. package/types-cjs/server.d.cts +23 -2
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@solidjs/web",
3
3
  "description": "Solid's web runtime: client rendering, hydration, SSR, and DOM-specific control flow (Portal, Dynamic).",
4
- "version": "2.0.0-rc.0",
4
+ "version": "2.0.0-rc.2",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "homepage": "https://solidjs.com",
@@ -378,10 +378,11 @@
378
378
  "seroval-plugins": "~1.5.4"
379
379
  },
380
380
  "peerDependencies": {
381
- "solid-js": "^2.0.0-rc.0"
381
+ "solid-js": "^2.0.0-rc.2"
382
382
  },
383
383
  "devDependencies": {
384
- "solid-js": "2.0.0-rc.0"
384
+ "@codspeed/vitest-plugin": "^5.4.0",
385
+ "solid-js": "2.0.0-rc.2"
385
386
  },
386
387
  "scripts": {
387
388
  "build": "npm-run-all -nl build:clean types:copy-jsx build:js",
@@ -8,6 +8,9 @@ const state = globalThis[STATE] || (globalThis[STATE] = {
8
8
  materialized: new WeakMap(),
9
9
  materializedValues: new WeakSet()
10
10
  });
11
+ function setContainerTraceStreamMint(fn) {
12
+ state.streamOf = fn;
13
+ }
11
14
  const TRACE = Symbol.for("dom-expressions.container-trace");
12
15
  function materialize(marker) {
13
16
  let value = state.materialized.get(marker.$tr);
@@ -20,9 +23,10 @@ function materialize(marker) {
20
23
  }
21
24
  function parseTrace(value, ctx) {
22
25
  const trace = value[TRACE];
26
+ const sub = trace.subscribe();
23
27
  return {
24
28
  a: trace.array ? 1 : 0,
25
- i: ctx.parse(trace.subscribe())
29
+ i: ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
26
30
  };
27
31
  }
28
32
  const ContainerTracePlugin = {
@@ -36,9 +40,10 @@ const ContainerTracePlugin = {
36
40
  },
37
41
  async async(value, ctx) {
38
42
  const trace = value[TRACE];
43
+ const sub = trace.subscribe();
39
44
  return {
40
45
  a: trace.array ? 1 : 0,
41
- i: await ctx.parse(trace.subscribe())
46
+ i: await ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
42
47
  };
43
48
  },
44
49
  stream: parseTrace
@@ -56,6 +61,18 @@ const ContainerTracePlugin = {
56
61
  }
57
62
  };
58
63
 
64
+ setContainerTraceStreamMint(iterable => {
65
+ const stream = seroval.createStream();
66
+ (async () => {
67
+ try {
68
+ for await (const value of iterable) stream.next(value);
69
+ stream.return(undefined);
70
+ } catch (error) {
71
+ stream.throw(error);
72
+ }
73
+ })();
74
+ return stream;
75
+ });
59
76
  const DEFAULT_WEB_PLUGINS = Object.freeze([web.AbortSignalPlugin,
60
77
  web.CustomEventPlugin, web.DOMExceptionPlugin, web.EventPlugin,
61
78
  web.FormDataPlugin, web.HeadersPlugin, web.ReadableStreamPlugin, web.RequestPlugin, web.ResponsePlugin, web.URLSearchParamsPlugin, web.URLPlugin,
@@ -79,12 +96,24 @@ function resolveCodecOptions({
79
96
  function createJSONDeserializer(options) {
80
97
  const refs = new Map();
81
98
  const resolved = resolveCodecOptions(options);
82
- return function deserializeJSONChunk(node) {
99
+ function deserializeJSONChunk(node) {
83
100
  return seroval.fromCrossJSON(node, {
84
101
  refs,
85
102
  ...resolved
86
103
  });
104
+ }
105
+ deserializeJSONChunk.abort = function abort(error) {
106
+ for (const value of refs.values()) {
107
+ if (value === null || typeof value !== "object") continue;
108
+ if (value.__SEROVAL_STREAM__) {
109
+ value.throw(error);
110
+ } else if (typeof value.s === "function" && typeof value.f === "function" && value.p instanceof Promise) {
111
+ value.p.then(undefined, () => {});
112
+ value.f(error);
113
+ }
114
+ }
87
115
  };
116
+ return deserializeJSONChunk;
88
117
  }
89
118
  function createJSONDataTable(options) {
90
119
  const deserialize = createJSONDeserializer(options);
@@ -1,4 +1,4 @@
1
- import { Feature, fromCrossJSON } from 'seroval';
1
+ import { Feature, fromCrossJSON, createStream } from 'seroval';
2
2
  import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
3
3
 
4
4
  const STATE = Symbol.for("dom-expressions.container-trace-state");
@@ -6,6 +6,9 @@ const state = globalThis[STATE] || (globalThis[STATE] = {
6
6
  materialized: new WeakMap(),
7
7
  materializedValues: new WeakSet()
8
8
  });
9
+ function setContainerTraceStreamMint(fn) {
10
+ state.streamOf = fn;
11
+ }
9
12
  const TRACE = Symbol.for("dom-expressions.container-trace");
10
13
  function materialize(marker) {
11
14
  let value = state.materialized.get(marker.$tr);
@@ -18,9 +21,10 @@ function materialize(marker) {
18
21
  }
19
22
  function parseTrace(value, ctx) {
20
23
  const trace = value[TRACE];
24
+ const sub = trace.subscribe();
21
25
  return {
22
26
  a: trace.array ? 1 : 0,
23
- i: ctx.parse(trace.subscribe())
27
+ i: ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
24
28
  };
25
29
  }
26
30
  const ContainerTracePlugin = {
@@ -34,9 +38,10 @@ const ContainerTracePlugin = {
34
38
  },
35
39
  async async(value, ctx) {
36
40
  const trace = value[TRACE];
41
+ const sub = trace.subscribe();
37
42
  return {
38
43
  a: trace.array ? 1 : 0,
39
- i: await ctx.parse(trace.subscribe())
44
+ i: await ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
40
45
  };
41
46
  },
42
47
  stream: parseTrace
@@ -54,6 +59,18 @@ const ContainerTracePlugin = {
54
59
  }
55
60
  };
56
61
 
62
+ setContainerTraceStreamMint(iterable => {
63
+ const stream = createStream();
64
+ (async () => {
65
+ try {
66
+ for await (const value of iterable) stream.next(value);
67
+ stream.return(undefined);
68
+ } catch (error) {
69
+ stream.throw(error);
70
+ }
71
+ })();
72
+ return stream;
73
+ });
57
74
  const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
58
75
  CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
59
76
  FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin,
@@ -77,12 +94,24 @@ function resolveCodecOptions({
77
94
  function createJSONDeserializer(options) {
78
95
  const refs = new Map();
79
96
  const resolved = resolveCodecOptions(options);
80
- return function deserializeJSONChunk(node) {
97
+ function deserializeJSONChunk(node) {
81
98
  return fromCrossJSON(node, {
82
99
  refs,
83
100
  ...resolved
84
101
  });
102
+ }
103
+ deserializeJSONChunk.abort = function abort(error) {
104
+ for (const value of refs.values()) {
105
+ if (value === null || typeof value !== "object") continue;
106
+ if (value.__SEROVAL_STREAM__) {
107
+ value.throw(error);
108
+ } else if (typeof value.s === "function" && typeof value.f === "function" && value.p instanceof Promise) {
109
+ value.p.then(undefined, () => {});
110
+ value.f(error);
111
+ }
112
+ }
85
113
  };
114
+ return deserializeJSONChunk;
86
115
  }
87
116
  function createJSONDataTable(options) {
88
117
  const deserialize = createJSONDeserializer(options);
@@ -8,6 +8,9 @@ const state = globalThis[STATE] || (globalThis[STATE] = {
8
8
  materialized: new WeakMap(),
9
9
  materializedValues: new WeakSet()
10
10
  });
11
+ function setContainerTraceStreamMint(fn) {
12
+ state.streamOf = fn;
13
+ }
11
14
  const TRACE = Symbol.for("dom-expressions.container-trace");
12
15
  function materialize(marker) {
13
16
  let value = state.materialized.get(marker.$tr);
@@ -20,9 +23,10 @@ function materialize(marker) {
20
23
  }
21
24
  function parseTrace(value, ctx) {
22
25
  const trace = value[TRACE];
26
+ const sub = trace.subscribe();
23
27
  return {
24
28
  a: trace.array ? 1 : 0,
25
- i: ctx.parse(trace.subscribe())
29
+ i: ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
26
30
  };
27
31
  }
28
32
  const ContainerTracePlugin = {
@@ -36,9 +40,10 @@ const ContainerTracePlugin = {
36
40
  },
37
41
  async async(value, ctx) {
38
42
  const trace = value[TRACE];
43
+ const sub = trace.subscribe();
39
44
  return {
40
45
  a: trace.array ? 1 : 0,
41
- i: await ctx.parse(trace.subscribe())
46
+ i: await ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
42
47
  };
43
48
  },
44
49
  stream: parseTrace
@@ -56,6 +61,18 @@ const ContainerTracePlugin = {
56
61
  }
57
62
  };
58
63
 
64
+ setContainerTraceStreamMint(iterable => {
65
+ const stream = seroval.createStream();
66
+ (async () => {
67
+ try {
68
+ for await (const value of iterable) stream.next(value);
69
+ stream.return(undefined);
70
+ } catch (error) {
71
+ stream.throw(error);
72
+ }
73
+ })();
74
+ return stream;
75
+ });
59
76
  const DEFAULT_WEB_PLUGINS = Object.freeze([web.AbortSignalPlugin,
60
77
  web.CustomEventPlugin, web.DOMExceptionPlugin, web.EventPlugin,
61
78
  web.FormDataPlugin, web.HeadersPlugin, web.ReadableStreamPlugin, web.RequestPlugin, web.ResponsePlugin, web.URLSearchParamsPlugin, web.URLPlugin,
@@ -79,12 +96,24 @@ function resolveCodecOptions({
79
96
  function createJSONDeserializer(options) {
80
97
  const refs = new Map();
81
98
  const resolved = resolveCodecOptions(options);
82
- return function deserializeJSONChunk(node) {
99
+ function deserializeJSONChunk(node) {
83
100
  return seroval.fromCrossJSON(node, {
84
101
  refs,
85
102
  ...resolved
86
103
  });
104
+ }
105
+ deserializeJSONChunk.abort = function abort(error) {
106
+ for (const value of refs.values()) {
107
+ if (value === null || typeof value !== "object") continue;
108
+ if (value.__SEROVAL_STREAM__) {
109
+ value.throw(error);
110
+ } else if (typeof value.s === "function" && typeof value.f === "function" && value.p instanceof Promise) {
111
+ value.p.then(undefined, () => {});
112
+ value.f(error);
113
+ }
114
+ }
87
115
  };
116
+ return deserializeJSONChunk;
88
117
  }
89
118
  function createJSONDataTable(options) {
90
119
  const deserialize = createJSONDeserializer(options);
@@ -1,4 +1,4 @@
1
- import { Feature, fromCrossJSON, toCrossJSONStream, Serializer, getCrossReferenceHeader } from 'seroval';
1
+ import { Feature, fromCrossJSON, createStream, toCrossJSONStream, Serializer, getCrossReferenceHeader } from 'seroval';
2
2
  export { OpaqueReference, createPlugin } from 'seroval';
3
3
  import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
4
4
 
@@ -7,6 +7,9 @@ const state = globalThis[STATE] || (globalThis[STATE] = {
7
7
  materialized: new WeakMap(),
8
8
  materializedValues: new WeakSet()
9
9
  });
10
+ function setContainerTraceStreamMint(fn) {
11
+ state.streamOf = fn;
12
+ }
10
13
  const TRACE = Symbol.for("dom-expressions.container-trace");
11
14
  function materialize(marker) {
12
15
  let value = state.materialized.get(marker.$tr);
@@ -19,9 +22,10 @@ function materialize(marker) {
19
22
  }
20
23
  function parseTrace(value, ctx) {
21
24
  const trace = value[TRACE];
25
+ const sub = trace.subscribe();
22
26
  return {
23
27
  a: trace.array ? 1 : 0,
24
- i: ctx.parse(trace.subscribe())
28
+ i: ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
25
29
  };
26
30
  }
27
31
  const ContainerTracePlugin = {
@@ -35,9 +39,10 @@ const ContainerTracePlugin = {
35
39
  },
36
40
  async async(value, ctx) {
37
41
  const trace = value[TRACE];
42
+ const sub = trace.subscribe();
38
43
  return {
39
44
  a: trace.array ? 1 : 0,
40
- i: await ctx.parse(trace.subscribe())
45
+ i: await ctx.parse(state.streamOf ? state.streamOf(sub) : sub)
41
46
  };
42
47
  },
43
48
  stream: parseTrace
@@ -55,6 +60,18 @@ const ContainerTracePlugin = {
55
60
  }
56
61
  };
57
62
 
63
+ setContainerTraceStreamMint(iterable => {
64
+ const stream = createStream();
65
+ (async () => {
66
+ try {
67
+ for await (const value of iterable) stream.next(value);
68
+ stream.return(undefined);
69
+ } catch (error) {
70
+ stream.throw(error);
71
+ }
72
+ })();
73
+ return stream;
74
+ });
58
75
  const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
59
76
  CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
60
77
  FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin,
@@ -78,12 +95,24 @@ function resolveCodecOptions({
78
95
  function createJSONDeserializer(options) {
79
96
  const refs = new Map();
80
97
  const resolved = resolveCodecOptions(options);
81
- return function deserializeJSONChunk(node) {
98
+ function deserializeJSONChunk(node) {
82
99
  return fromCrossJSON(node, {
83
100
  refs,
84
101
  ...resolved
85
102
  });
103
+ }
104
+ deserializeJSONChunk.abort = function abort(error) {
105
+ for (const value of refs.values()) {
106
+ if (value === null || typeof value !== "object") continue;
107
+ if (value.__SEROVAL_STREAM__) {
108
+ value.throw(error);
109
+ } else if (typeof value.s === "function" && typeof value.f === "function" && value.p instanceof Promise) {
110
+ value.p.then(undefined, () => {});
111
+ value.f(error);
112
+ }
113
+ }
86
114
  };
115
+ return deserializeJSONChunk;
87
116
  }
88
117
  function createJSONDataTable(options) {
89
118
  const deserialize = createJSONDeserializer(options);
@@ -18,6 +18,7 @@ function withMeta(fn, meta) {
18
18
  Object.assign(metadata, meta);
19
19
  return fn;
20
20
  }
21
+ const LIVE_SOURCE = Symbol.for("solid.LiveSource");
21
22
  const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
22
23
  function provideServerFunctionRPC(rpc) {
23
24
  globalThis[SERVER_FUNCTION_RPC] || (globalThis[SERVER_FUNCTION_RPC] = rpc);
@@ -138,7 +139,7 @@ const BodyFormat = {
138
139
  Uint8Array: "7",
139
140
  Json: "8"
140
141
  };
141
- const JSON_SAFE_DEPTH_LIMIT = 10000;
142
+ const JSON_SAFE_DEPTH_LIMIT = 4096;
142
143
  const EXIT = {};
143
144
  function isJSONSafe(value) {
144
145
  const stack = [value];
@@ -165,6 +166,7 @@ function isJSONSafe(value) {
165
166
  } else {
166
167
  const proto = Object.getPrototypeOf(v);
167
168
  if (proto !== Object.prototype && proto !== null) return false;
169
+ if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
168
170
  for (const k in v) stack.push(v[k]);
169
171
  }
170
172
  }
@@ -366,7 +368,7 @@ async function deserializeStream(source, codecOptions) {
366
368
  function interpretChunk(chunk) {
367
369
  return deserializeChunk(JSON.parse(chunk));
368
370
  }
369
- void reader.drain(interpretChunk);
371
+ reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
370
372
  return interpretChunk(result.value);
371
373
  }
372
374
  return undefined;
@@ -394,6 +396,30 @@ const config = {
394
396
  responseHandler: undefined,
395
397
  serializeArgs: undefined
396
398
  };
399
+ const CALL_OBSERVERS = new Set();
400
+ function notifyCallObservers(type, id, instance, value, meta) {
401
+ if (CALL_OBSERVERS.size === 0) return;
402
+ const field = type === "request" ? "request" : "response";
403
+ const time = performance.now();
404
+ for (const observer of new Set(CALL_OBSERVERS)) {
405
+ try {
406
+ observer({
407
+ type,
408
+ id,
409
+ instance,
410
+ [field]: value.clone(),
411
+ meta,
412
+ time
413
+ });
414
+ } catch (error) {
415
+ console.error(error);
416
+ }
417
+ }
418
+ }
419
+ function observeServerFunctionCalls(observer) {
420
+ CALL_OBSERVERS.add(observer);
421
+ return () => CALL_OBSERVERS.delete(observer);
422
+ }
397
423
  function serializeArguments(args) {
398
424
  if (!config.serializeArgs) {
399
425
  throw new Error("Server function arguments are sent as JSON by default and these " + "arguments are not JSON-serializable. Call enableRichArguments() " + '(from "@solidjs/web/server-functions/rich-args") once at startup ' + "to send Dates, Maps, Sets, typed arrays, etc. through the codec — " + "or pass a single Blob/FormData/File argument, which has a native " + "HTTP encoding.");
@@ -423,13 +449,18 @@ function provideRPC() {
423
449
  decodeResponse
424
450
  });
425
451
  }
452
+ function serverFunctionFailure(response, value) {
453
+ const error = value ?? new Error(`Server function call failed with status ${response.status}`);
454
+ if (error instanceof Error && !("status" in error)) error.status = response.status;
455
+ return error;
456
+ }
426
457
  async function createRequest(base, id, instance, options, meta) {
427
458
  const headers = {
428
459
  ...options.headers,
429
460
  [FUNCTION_HEADER]: id,
430
461
  [INSTANCE_HEADER]: instance
431
462
  };
432
- if (getFlightDataConsumer() && (!options.method || options.method.toUpperCase() !== "GET")) {
463
+ if (getFlightDataConsumer() && !options.read && (!options.method || options.method.toUpperCase() !== "GET")) {
433
464
  headers[SINGLE_FLIGHT_HEADER] = "true";
434
465
  }
435
466
  let init = {
@@ -443,7 +474,12 @@ async function createRequest(base, id, instance, options, meta) {
443
474
  meta
444
475
  })) || init;
445
476
  }
446
- return fetch(base, init);
477
+ if (CALL_OBSERVERS.size === 0) return fetch(base, init);
478
+ const request = new Request(new URL(base, globalThis.location?.href || "http://localhost"), init);
479
+ notifyCallObservers("request", id, instance, request, meta);
480
+ const response = await fetch(request);
481
+ notifyCallObservers("response", id, instance, response, meta);
482
+ return response;
447
483
  }
448
484
  async function initializeResponse(base, id, instance, options, args, meta) {
449
485
  if (args.length === 0) {
@@ -511,6 +547,11 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
511
547
  id,
512
548
  meta
513
549
  }) : undefined;
550
+ const controller = options.signal ? undefined : new AbortController();
551
+ if (controller) options = {
552
+ ...options,
553
+ signal: controller.signal
554
+ };
514
555
  const response = await initializeResponse(base, id, instance, options, args, meta);
515
556
  if (handler) {
516
557
  const handled = handler.handle(response, {
@@ -521,6 +562,7 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
521
562
  });
522
563
  if (handled !== undefined) return handled;
523
564
  }
565
+ const failed = response.headers.has(ERROR_HEADER) || response.status >= 500;
524
566
  if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
525
567
  const consumer = getFlightDataConsumer();
526
568
  if (consumer) {
@@ -528,8 +570,8 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
528
570
  await consumer(payload.data, {
529
571
  response
530
572
  });
531
- if (response.headers.has(ERROR_HEADER) && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
532
- throw payload.value;
573
+ if (failed && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
574
+ throw serverFunctionFailure(response, payload.value);
533
575
  }
534
576
  return payload.value;
535
577
  }
@@ -538,8 +580,22 @@ async function fetchServerFunction(base, id, options, args, meta, callArgs = arg
538
580
  return response;
539
581
  }
540
582
  const result = await decodeResponse(response.clone());
541
- if (response.headers.has(ERROR_HEADER)) {
542
- throw result;
583
+ if (failed) {
584
+ throw serverFunctionFailure(response, result);
585
+ }
586
+ if (controller && result?.[Symbol.asyncIterator]) {
587
+ return {
588
+ [Symbol.asyncIterator]() {
589
+ const it = result[Symbol.asyncIterator]();
590
+ return {
591
+ next: () => it.next(),
592
+ return: value => (controller.abort(), Promise.resolve({
593
+ done: true,
594
+ value
595
+ }))
596
+ };
597
+ }
598
+ };
543
599
  }
544
600
  return result;
545
601
  }
@@ -609,6 +665,136 @@ function GET(fn) {
609
665
  method: "GET"
610
666
  });
611
667
  }
668
+ function live(fn) {
669
+ if (!isServerFunction(fn)) {
670
+ throw new Error("live expects a server function reference");
671
+ }
672
+ const id = fn.id;
673
+ const metadata = {
674
+ ...getServerFunctionMetadata(fn),
675
+ live: true
676
+ };
677
+ const wrapped = (...args) => {
678
+ const iterable = {
679
+ [LIVE_SOURCE]: true,
680
+ [Symbol.asyncIterator]() {
681
+ let it;
682
+ let connected = false;
683
+ let attempts = 0;
684
+ let stopped = false;
685
+ let ended = false;
686
+ let timer, wake;
687
+ const DONE = {
688
+ done: true,
689
+ value: undefined
690
+ };
691
+ const emit = (state, error) => {
692
+ try {
693
+ iterable.onstatus && iterable.onstatus(state, error);
694
+ } catch {}
695
+ };
696
+ const emitClosed = error => {
697
+ if (ended) return;
698
+ ended = true;
699
+ emit("closed", error);
700
+ };
701
+ const closeIt = value => {
702
+ const current = it;
703
+ it = undefined;
704
+ if (current) {
705
+ try {
706
+ const r = current.return && current.return(value);
707
+ if (r && typeof r.then === "function") r.then(undefined, () => {});
708
+ } catch {}
709
+ }
710
+ };
711
+ const callOnce = () => {
712
+ if (metadata.method === "GET") return fn(...args);
713
+ const handler = config.responseHandler;
714
+ if (handler && handler.intercept) {
715
+ const hit = handler.intercept({
716
+ id,
717
+ meta: metadata,
718
+ args
719
+ });
720
+ if (hit !== undefined) return hit;
721
+ }
722
+ return fetchServerFunction(fn.url, id, {
723
+ read: true
724
+ }, args, metadata, args);
725
+ };
726
+ const pull = async () => {
727
+ while (!stopped) {
728
+ try {
729
+ if (!it) {
730
+ const result = await callOnce();
731
+ connected = true;
732
+ it = result !== null && typeof result === "object" && result[Symbol.asyncIterator] ? result[Symbol.asyncIterator]() : async function* () {
733
+ yield result;
734
+ }();
735
+ if (stopped) {
736
+ closeIt();
737
+ return DONE;
738
+ }
739
+ emit("connected");
740
+ }
741
+ const r = await it.next();
742
+ if (r.done) {
743
+ emitClosed();
744
+ return DONE;
745
+ }
746
+ if (stopped) return DONE;
747
+ attempts = 0;
748
+ return r;
749
+ } catch (error) {
750
+ if (!connected) throw error;
751
+ if (error !== null && typeof error === "object" && typeof error.status === "number" && error.status >= 400 && error.status < 500) {
752
+ stopped = true;
753
+ emitClosed(error);
754
+ throw error;
755
+ }
756
+ it = undefined;
757
+ emit("reconnecting", error);
758
+ await new Promise(resolve => {
759
+ wake = resolve;
760
+ timer = setTimeout(resolve, Math.min(500 * 2 ** attempts++, 10000));
761
+ if (typeof addEventListener === "function") addEventListener("online", resolve, {
762
+ once: true
763
+ });
764
+ });
765
+ clearTimeout(timer);
766
+ if (typeof removeEventListener === "function") removeEventListener("online", wake);
767
+ timer = wake = undefined;
768
+ }
769
+ }
770
+ return DONE;
771
+ };
772
+ return {
773
+ next: () => pull(),
774
+ return(value) {
775
+ stopped = true;
776
+ if (timer !== undefined) clearTimeout(timer);
777
+ if (wake) wake();
778
+ closeIt(value);
779
+ emitClosed();
780
+ return Promise.resolve({
781
+ done: true,
782
+ value
783
+ });
784
+ }
785
+ };
786
+ }
787
+ };
788
+ return iterable;
789
+ };
790
+ wrapped[SERVER_FUNCTION_METADATA] = metadata;
791
+ wrapped.id = id;
792
+ Object.defineProperty(wrapped, "url", {
793
+ get: () => fn.url,
794
+ configurable: true
795
+ });
796
+ return wrapped;
797
+ }
612
798
  function registerServerReference() {
613
799
  throw new Error("registerServerReference must not be called in the client build");
614
800
  }
@@ -640,6 +826,8 @@ exports.getServerFunctionMetadata = getServerFunctionMetadata;
640
826
  exports.getServerFunctionsCodec = getServerFunctionsCodec;
641
827
  exports.hasFlashCookie = hasFlashCookie;
642
828
  exports.isServerFunction = isServerFunction;
829
+ exports.live = live;
830
+ exports.observeServerFunctionCalls = observeServerFunctionCalls;
643
831
  exports.registerServerReference = registerServerReference;
644
832
  exports.serializeString = serializeString;
645
833
  exports.subscribeFlightData = subscribeFlightData;